@netloc8/nextjs 1.0.0 → 1.1.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.mjs +1 -2
- package/dist/proxy.d.mts.map +1 -1
- package/dist/proxy.mjs +3 -373
- package/dist/proxy.mjs.map +1 -1
- package/dist/server.mjs +1 -33
- package/dist/server.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1,2 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import
|
|
3
|
-
export { GeoContext, GeoGate, NetLoc8Provider, useGeo };
|
|
2
|
+
import{GeoContext as e,GeoGate as t,NetLoc8Provider as n,useGeo as r}from"@netloc8/react";export{e as GeoContext,t as GeoGate,n as NetLoc8Provider,r as useGeo};
|
package/dist/proxy.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"proxy.d.mts","names":[],"sources":["../src/proxy.ts"],"mappings":";;;;UAmBU,kBAAA;EACN,OAAA;EACA,MAAA;EACA,MAAA;EACA,MAAA;EACA,OAAA,IACI,OAAA,EAAS,WAAA,EACT,GAAA,EAAK,GAAA,KACJ,YAAA,eAA2B,OAAA,CAAQ,YAAA;AAAA;AAAA,UAGlC,kBAAA;EACN,aAAA;EACA,SAAA,EAAW,MAAA;EACX,YAAA;AAAA;;;;;iBAiPY,cAAA,CAAe,OAAA,EAAS,OAAA,GAAU,GAAA;;;;;;;;iBA2BlC,WAAA,CAAY,OAAA,GAAU,kBAAA,IACjC,OAAA,EAAS,WAAA,KAAgB,OAAA,CAAQ,YAAA;AAnRM;;;AAAA,
|
|
1
|
+
{"version":3,"file":"proxy.d.mts","names":[],"sources":["../src/proxy.ts"],"mappings":";;;;UAmBU,kBAAA;EACN,OAAA;EACA,MAAA;EACA,MAAA;EACA,MAAA;EACA,OAAA,IACI,OAAA,EAAS,WAAA,EACT,GAAA,EAAK,GAAA,KACJ,YAAA,eAA2B,OAAA,CAAQ,YAAA;AAAA;AAAA,UAGlC,kBAAA;EACN,aAAA;EACA,SAAA,EAAW,MAAA;EACX,YAAA;AAAA;;;;;iBAiPY,cAAA,CAAe,OAAA,EAAS,OAAA,GAAU,GAAA;;;;;;;;iBA2BlC,WAAA,CAAY,OAAA,GAAU,kBAAA,IACjC,OAAA,EAAS,WAAA,KAAgB,OAAA,CAAQ,YAAA;AAnRM;;;AAAA,iBAia5B,eAAA,CACZ,OAAA,EAAS,kBAAA,IACT,OAAA,EAAS,WAAA,EAAa,GAAA,EAAK,GAAA,KAAQ,YAAA"}
|
package/dist/proxy.mjs
CHANGED
|
@@ -1,374 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const HEADER_ENTRIES = [
|
|
5
|
-
{
|
|
6
|
-
header: "x-netloc8-ip",
|
|
7
|
-
get: (g) => g.query?.value,
|
|
8
|
-
set: (g, v) => {
|
|
9
|
-
if (!g.query) g.query = {};
|
|
10
|
-
g.query.value = v;
|
|
11
|
-
},
|
|
12
|
-
type: "string"
|
|
13
|
-
},
|
|
14
|
-
{
|
|
15
|
-
header: "x-netloc8-ip-version",
|
|
16
|
-
get: (g) => g.query?.ipVersion,
|
|
17
|
-
set: (g, v) => {
|
|
18
|
-
const n = parseFloat(v);
|
|
19
|
-
if (!Number.isFinite(n)) return;
|
|
20
|
-
if (!g.query) g.query = {};
|
|
21
|
-
g.query.ipVersion = n;
|
|
22
|
-
},
|
|
23
|
-
type: "number"
|
|
24
|
-
},
|
|
25
|
-
{
|
|
26
|
-
header: "x-netloc8-continent-code",
|
|
27
|
-
get: (g) => g.location?.continent?.code,
|
|
28
|
-
set: (g, v) => {
|
|
29
|
-
if (!g.location) g.location = {};
|
|
30
|
-
if (!g.location.continent) g.location.continent = {};
|
|
31
|
-
g.location.continent.code = v;
|
|
32
|
-
},
|
|
33
|
-
type: "string"
|
|
34
|
-
},
|
|
35
|
-
{
|
|
36
|
-
header: "x-netloc8-continent-name",
|
|
37
|
-
get: (g) => g.location?.continent?.name,
|
|
38
|
-
set: (g, v) => {
|
|
39
|
-
if (!g.location) g.location = {};
|
|
40
|
-
if (!g.location.continent) g.location.continent = {};
|
|
41
|
-
g.location.continent.name = v;
|
|
42
|
-
},
|
|
43
|
-
type: "string"
|
|
44
|
-
},
|
|
45
|
-
{
|
|
46
|
-
header: "x-netloc8-country-code",
|
|
47
|
-
get: (g) => g.location?.country?.code,
|
|
48
|
-
set: (g, v) => {
|
|
49
|
-
if (!g.location) g.location = {};
|
|
50
|
-
if (!g.location.country) g.location.country = {};
|
|
51
|
-
g.location.country.code = v;
|
|
52
|
-
},
|
|
53
|
-
type: "string"
|
|
54
|
-
},
|
|
55
|
-
{
|
|
56
|
-
header: "x-netloc8-country-name",
|
|
57
|
-
get: (g) => g.location?.country?.name,
|
|
58
|
-
set: (g, v) => {
|
|
59
|
-
if (!g.location) g.location = {};
|
|
60
|
-
if (!g.location.country) g.location.country = {};
|
|
61
|
-
g.location.country.name = v;
|
|
62
|
-
},
|
|
63
|
-
type: "string"
|
|
64
|
-
},
|
|
65
|
-
{
|
|
66
|
-
header: "x-netloc8-country-flag",
|
|
67
|
-
get: (g) => g.location?.country?.flag,
|
|
68
|
-
set: (g, v) => {
|
|
69
|
-
if (!g.location) g.location = {};
|
|
70
|
-
if (!g.location.country) g.location.country = {};
|
|
71
|
-
g.location.country.flag = v;
|
|
72
|
-
},
|
|
73
|
-
type: "string"
|
|
74
|
-
},
|
|
75
|
-
{
|
|
76
|
-
header: "x-netloc8-country-unions",
|
|
77
|
-
get: (g) => g.location?.country?.unions,
|
|
78
|
-
set: (g, v) => {
|
|
79
|
-
if (!g.location) g.location = {};
|
|
80
|
-
if (!g.location.country) g.location.country = {};
|
|
81
|
-
try {
|
|
82
|
-
const parsed = JSON.parse(v);
|
|
83
|
-
if (Array.isArray(parsed)) g.location.country.unions = parsed;
|
|
84
|
-
} catch {}
|
|
85
|
-
},
|
|
86
|
-
type: "json"
|
|
87
|
-
},
|
|
88
|
-
{
|
|
89
|
-
header: "x-netloc8-region-code",
|
|
90
|
-
get: (g) => g.location?.region?.code,
|
|
91
|
-
set: (g, v) => {
|
|
92
|
-
if (!g.location) g.location = {};
|
|
93
|
-
if (!g.location.region) g.location.region = {};
|
|
94
|
-
g.location.region.code = v;
|
|
95
|
-
},
|
|
96
|
-
type: "string"
|
|
97
|
-
},
|
|
98
|
-
{
|
|
99
|
-
header: "x-netloc8-region-name",
|
|
100
|
-
get: (g) => g.location?.region?.name,
|
|
101
|
-
set: (g, v) => {
|
|
102
|
-
if (!g.location) g.location = {};
|
|
103
|
-
if (!g.location.region) g.location.region = {};
|
|
104
|
-
g.location.region.name = v;
|
|
105
|
-
},
|
|
106
|
-
type: "string"
|
|
107
|
-
},
|
|
108
|
-
{
|
|
109
|
-
header: "x-netloc8-district",
|
|
110
|
-
get: (g) => g.location?.district,
|
|
111
|
-
set: (g, v) => {
|
|
112
|
-
if (!g.location) g.location = {};
|
|
113
|
-
g.location.district = v;
|
|
114
|
-
},
|
|
115
|
-
type: "string"
|
|
116
|
-
},
|
|
117
|
-
{
|
|
118
|
-
header: "x-netloc8-city",
|
|
119
|
-
get: (g) => g.location?.city,
|
|
120
|
-
set: (g, v) => {
|
|
121
|
-
if (!g.location) g.location = {};
|
|
122
|
-
g.location.city = v;
|
|
123
|
-
},
|
|
124
|
-
type: "string"
|
|
125
|
-
},
|
|
126
|
-
{
|
|
127
|
-
header: "x-netloc8-postal-code",
|
|
128
|
-
get: (g) => g.location?.postalCode,
|
|
129
|
-
set: (g, v) => {
|
|
130
|
-
if (!g.location) g.location = {};
|
|
131
|
-
g.location.postalCode = v;
|
|
132
|
-
},
|
|
133
|
-
type: "string"
|
|
134
|
-
},
|
|
135
|
-
{
|
|
136
|
-
header: "x-netloc8-latitude",
|
|
137
|
-
get: (g) => g.location?.coordinates?.latitude,
|
|
138
|
-
set: (g, v) => {
|
|
139
|
-
if (!g.location) g.location = {};
|
|
140
|
-
if (!g.location.coordinates) g.location.coordinates = {};
|
|
141
|
-
const n = parseFloat(v);
|
|
142
|
-
if (!Number.isFinite(n)) return;
|
|
143
|
-
g.location.coordinates.latitude = n;
|
|
144
|
-
},
|
|
145
|
-
type: "number"
|
|
146
|
-
},
|
|
147
|
-
{
|
|
148
|
-
header: "x-netloc8-longitude",
|
|
149
|
-
get: (g) => g.location?.coordinates?.longitude,
|
|
150
|
-
set: (g, v) => {
|
|
151
|
-
if (!g.location) g.location = {};
|
|
152
|
-
if (!g.location.coordinates) g.location.coordinates = {};
|
|
153
|
-
const n = parseFloat(v);
|
|
154
|
-
if (!Number.isFinite(n)) return;
|
|
155
|
-
g.location.coordinates.longitude = n;
|
|
156
|
-
},
|
|
157
|
-
type: "number"
|
|
158
|
-
},
|
|
159
|
-
{
|
|
160
|
-
header: "x-netloc8-accuracy-radius",
|
|
161
|
-
get: (g) => g.location?.coordinates?.accuracyRadius,
|
|
162
|
-
set: (g, v) => {
|
|
163
|
-
if (!g.location) g.location = {};
|
|
164
|
-
if (!g.location.coordinates) g.location.coordinates = {};
|
|
165
|
-
const n = parseFloat(v);
|
|
166
|
-
if (!Number.isFinite(n)) return;
|
|
167
|
-
g.location.coordinates.accuracyRadius = n;
|
|
168
|
-
},
|
|
169
|
-
type: "number"
|
|
170
|
-
},
|
|
171
|
-
{
|
|
172
|
-
header: "x-netloc8-timezone",
|
|
173
|
-
get: (g) => g.location?.timezone,
|
|
174
|
-
set: (g, v) => {
|
|
175
|
-
if (!g.location) g.location = {};
|
|
176
|
-
g.location.timezone = v;
|
|
177
|
-
},
|
|
178
|
-
type: "string"
|
|
179
|
-
},
|
|
180
|
-
{
|
|
181
|
-
header: "x-netloc8-utc-offset",
|
|
182
|
-
get: (g) => g.location?.utcOffset,
|
|
183
|
-
set: (g, v) => {
|
|
184
|
-
if (!g.location) g.location = {};
|
|
185
|
-
g.location.utcOffset = v;
|
|
186
|
-
},
|
|
187
|
-
type: "string"
|
|
188
|
-
},
|
|
189
|
-
{
|
|
190
|
-
header: "x-netloc8-geo-confidence",
|
|
191
|
-
get: (g) => g.location?.geoConfidence,
|
|
192
|
-
set: (g, v) => {
|
|
193
|
-
const n = parseFloat(v);
|
|
194
|
-
if (!Number.isFinite(n)) return;
|
|
195
|
-
if (!g.location) g.location = {};
|
|
196
|
-
g.location.geoConfidence = n;
|
|
197
|
-
},
|
|
198
|
-
type: "number"
|
|
199
|
-
},
|
|
200
|
-
{
|
|
201
|
-
header: "x-netloc8-asn",
|
|
202
|
-
get: (g) => g.network?.asn,
|
|
203
|
-
set: (g, v) => {
|
|
204
|
-
if (!g.network) g.network = {};
|
|
205
|
-
g.network.asn = v;
|
|
206
|
-
},
|
|
207
|
-
type: "string"
|
|
208
|
-
},
|
|
209
|
-
{
|
|
210
|
-
header: "x-netloc8-asn-org",
|
|
211
|
-
get: (g) => g.network?.organization,
|
|
212
|
-
set: (g, v) => {
|
|
213
|
-
if (!g.network) g.network = {};
|
|
214
|
-
g.network.organization = v;
|
|
215
|
-
},
|
|
216
|
-
type: "string"
|
|
217
|
-
},
|
|
218
|
-
{
|
|
219
|
-
header: "x-netloc8-asn-domain",
|
|
220
|
-
get: (g) => g.network?.domain,
|
|
221
|
-
set: (g, v) => {
|
|
222
|
-
if (!g.network) g.network = {};
|
|
223
|
-
g.network.domain = v;
|
|
224
|
-
},
|
|
225
|
-
type: "string"
|
|
226
|
-
},
|
|
227
|
-
{
|
|
228
|
-
header: "x-netloc8-precision",
|
|
229
|
-
get: (g) => g.meta?.precision,
|
|
230
|
-
set: (g, v) => {
|
|
231
|
-
if (!g.meta) g.meta = {};
|
|
232
|
-
g.meta.precision = v;
|
|
233
|
-
},
|
|
234
|
-
type: "string"
|
|
235
|
-
},
|
|
236
|
-
{
|
|
237
|
-
header: "x-netloc8-degraded",
|
|
238
|
-
get: (g) => g.meta?.degraded,
|
|
239
|
-
set: (g, v) => {
|
|
240
|
-
if (!g.meta) g.meta = {};
|
|
241
|
-
g.meta.degraded = v === "true";
|
|
242
|
-
},
|
|
243
|
-
type: "boolean"
|
|
244
|
-
},
|
|
245
|
-
{
|
|
246
|
-
header: "x-netloc8-timezone-from-client",
|
|
247
|
-
get: (g) => g.location?.timezoneFromClient,
|
|
248
|
-
set: (g, v) => {
|
|
249
|
-
if (!g.location) g.location = {};
|
|
250
|
-
g.location.timezoneFromClient = v === "true";
|
|
251
|
-
},
|
|
252
|
-
type: "boolean"
|
|
253
|
-
}
|
|
254
|
-
];
|
|
255
|
-
/**
|
|
256
|
-
* Set x-netloc8-* request headers from a Geo object.
|
|
257
|
-
*/
|
|
258
|
-
function setGeoHeaders(requestHeaders, geo) {
|
|
259
|
-
for (const entry of HEADER_ENTRIES) {
|
|
260
|
-
const value = entry.get(geo);
|
|
261
|
-
if (value !== void 0 && value !== null) if (entry.type === "json") requestHeaders.set(entry.header, encodeURIComponent(JSON.stringify(value)));
|
|
262
|
-
else requestHeaders.set(entry.header, encodeURIComponent(String(value)));
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
/**
|
|
266
|
-
* Read x-netloc8-* request headers back into a Geo object.
|
|
267
|
-
* Used by server.ts to reconstruct Geo on the server side.
|
|
268
|
-
*/
|
|
269
|
-
function readGeoHeaders(headers) {
|
|
270
|
-
const geo = {};
|
|
271
|
-
for (const entry of HEADER_ENTRIES) {
|
|
272
|
-
const raw = headers.get(entry.header);
|
|
273
|
-
if (raw === null) continue;
|
|
274
|
-
try {
|
|
275
|
-
const decoded = decodeURIComponent(raw);
|
|
276
|
-
entry.set(geo, decoded);
|
|
277
|
-
} catch {}
|
|
278
|
-
}
|
|
279
|
-
return geo;
|
|
280
|
-
}
|
|
281
|
-
/**
|
|
282
|
-
* Create a Next.js 16 proxy function that resolves geolocation for every
|
|
283
|
-
* matching request.
|
|
284
|
-
*
|
|
285
|
-
* Returns a standard proxy function that can be exported directly from the
|
|
286
|
-
* user's proxy.ts / proxy.js file, or composed with other proxy logic.
|
|
287
|
-
*/
|
|
288
|
-
function createProxy(options) {
|
|
289
|
-
return async (request) => {
|
|
290
|
-
const apiKey = options?.apiKey ?? process.env.NETLOC8_API_KEY;
|
|
291
|
-
const apiUrl = options?.apiUrl ?? process.env.NETLOC8_API_URL;
|
|
292
|
-
const timeout = options?.timeout ?? 1500;
|
|
293
|
-
const requestHeaders = new Headers(request.headers);
|
|
294
|
-
for (const entry of HEADER_ENTRIES) requestHeaders.delete(entry.header);
|
|
295
|
-
let clientIp;
|
|
296
|
-
if (process.env.NODE_ENV !== "production") clientIp = options?.testIp ?? process.env.NETLOC8_TEST_IP;
|
|
297
|
-
if (!clientIp) clientIp = getClientIp(request.headers);
|
|
298
|
-
const cookieValue = request.cookies.get(COOKIE_NAME)?.value;
|
|
299
|
-
const cookieGeo = parseCookie(cookieValue);
|
|
300
|
-
const cookieTimezone = cookieGeo.location?.timezoneFromClient === true && cookieGeo.query?.value === clientIp ? {
|
|
301
|
-
timezone: cookieGeo.location.timezone,
|
|
302
|
-
timezoneFromClient: cookieGeo.location.timezoneFromClient
|
|
303
|
-
} : void 0;
|
|
304
|
-
const platformGeo = getGeoFromPlatformHeaders(request.headers);
|
|
305
|
-
let apiGeo;
|
|
306
|
-
if (clientIp && isPublicIp(clientIp) && !platformGeo.location?.country?.code && !cookieTimezone) {
|
|
307
|
-
const raw = await fetchGeo(clientIp, {
|
|
308
|
-
apiKey,
|
|
309
|
-
apiUrl,
|
|
310
|
-
timeout,
|
|
311
|
-
clientId: `@netloc8/nextjs/1.0.0`
|
|
312
|
-
});
|
|
313
|
-
if (raw) apiGeo = normalizeApiResponse(raw, clientIp);
|
|
314
|
-
}
|
|
315
|
-
const geo = reconcileGeo({
|
|
316
|
-
cookie: cookieGeo.query?.value ? cookieGeo : void 0,
|
|
317
|
-
platform: platformGeo,
|
|
318
|
-
api: apiGeo,
|
|
319
|
-
ip: clientIp
|
|
320
|
-
});
|
|
321
|
-
if (cookieTimezone) {
|
|
322
|
-
if (!geo.location) geo.location = {};
|
|
323
|
-
geo.location.timezone = cookieTimezone.timezone;
|
|
324
|
-
geo.location.timezoneFromClient = cookieTimezone.timezoneFromClient;
|
|
325
|
-
}
|
|
326
|
-
setGeoHeaders(requestHeaders, geo);
|
|
327
|
-
let handlerResponse;
|
|
328
|
-
if (options?.handler) {
|
|
329
|
-
const sanitizedRequest = new Request(request.nextUrl.toString(), {
|
|
330
|
-
method: request.method ?? "GET",
|
|
331
|
-
headers: requestHeaders,
|
|
332
|
-
body: request.body,
|
|
333
|
-
duplex: "half"
|
|
334
|
-
});
|
|
335
|
-
handlerResponse = await options.handler(Object.assign(sanitizedRequest, {
|
|
336
|
-
nextUrl: request.nextUrl,
|
|
337
|
-
cookies: request.cookies
|
|
338
|
-
}), geo);
|
|
339
|
-
}
|
|
340
|
-
const response = handlerResponse ?? NextResponse.next({ request: { headers: requestHeaders } });
|
|
341
|
-
if (!cookieValue || cookieGeo.query?.value !== clientIp) response.cookies.set(COOKIE_NAME, serializeCookie(geo), {
|
|
342
|
-
path: COOKIE_OPTIONS.path,
|
|
343
|
-
httpOnly: COOKIE_OPTIONS.httpOnly,
|
|
344
|
-
secure: COOKIE_OPTIONS.secure,
|
|
345
|
-
sameSite: COOKIE_OPTIONS.sameSite,
|
|
346
|
-
maxAge: COOKIE_OPTIONS.maxAge
|
|
347
|
-
});
|
|
348
|
-
return response;
|
|
349
|
-
};
|
|
350
|
-
}
|
|
351
|
-
/**
|
|
352
|
-
* Create a geo-redirect handler for use with createProxy.
|
|
353
|
-
*/
|
|
354
|
-
function withGeoRedirect(options) {
|
|
355
|
-
const { defaultLocale, localeMap, excludePaths = [] } = options;
|
|
356
|
-
const validLocales = new Set(Object.values(localeMap));
|
|
357
|
-
validLocales.add(defaultLocale);
|
|
358
|
-
return (request, geo) => {
|
|
359
|
-
const pathname = request.nextUrl.pathname;
|
|
360
|
-
for (const prefix of excludePaths) if (pathname.startsWith(prefix)) return;
|
|
361
|
-
const currentPrefix = pathname.split("/").filter(Boolean)[0];
|
|
362
|
-
if (currentPrefix && validLocales.has(currentPrefix)) return;
|
|
363
|
-
const countryCode = geo.location?.country?.code;
|
|
364
|
-
const locale = countryCode && localeMap[countryCode] || defaultLocale;
|
|
365
|
-
if (locale === defaultLocale) return;
|
|
366
|
-
const url = request.nextUrl.clone();
|
|
367
|
-
url.pathname = `/${locale}${pathname}`;
|
|
368
|
-
return NextResponse.redirect(url, 307);
|
|
369
|
-
};
|
|
370
|
-
}
|
|
371
|
-
//#endregion
|
|
372
|
-
export { createProxy, readGeoHeaders, withGeoRedirect };
|
|
373
|
-
|
|
1
|
+
import{NextResponse as e}from"next/server";import{COOKIE_NAME as t,COOKIE_OPTIONS as n,fetchGeo as r,getClientIp as i,getGeoFromPlatformHeaders as a,isPublicIp as o,normalizeApiResponse as s,parseCookie as c,reconcileGeo as l,serializeCookie as u}from"@netloc8/core";const d=[{header:`x-netloc8-ip`,get:e=>e.query?.value,set:(e,t)=>{e.query||={},e.query.value=t},type:`string`},{header:`x-netloc8-ip-version`,get:e=>e.query?.ipVersion,set:(e,t)=>{let n=parseFloat(t);Number.isFinite(n)&&(e.query||={},e.query.ipVersion=n)},type:`number`},{header:`x-netloc8-continent-code`,get:e=>e.location?.continent?.code,set:(e,t)=>{e.location||={},e.location.continent||(e.location.continent={}),e.location.continent.code=t},type:`string`},{header:`x-netloc8-continent-name`,get:e=>e.location?.continent?.name,set:(e,t)=>{e.location||={},e.location.continent||(e.location.continent={}),e.location.continent.name=t},type:`string`},{header:`x-netloc8-country-code`,get:e=>e.location?.country?.code,set:(e,t)=>{e.location||={},e.location.country||(e.location.country={}),e.location.country.code=t},type:`string`},{header:`x-netloc8-country-name`,get:e=>e.location?.country?.name,set:(e,t)=>{e.location||={},e.location.country||(e.location.country={}),e.location.country.name=t},type:`string`},{header:`x-netloc8-country-flag`,get:e=>e.location?.country?.flag,set:(e,t)=>{e.location||={},e.location.country||(e.location.country={}),e.location.country.flag=t},type:`string`},{header:`x-netloc8-country-unions`,get:e=>e.location?.country?.unions,set:(e,t)=>{e.location||={},e.location.country||(e.location.country={});try{let n=JSON.parse(t);Array.isArray(n)&&(e.location.country.unions=n)}catch{}},type:`json`},{header:`x-netloc8-region-code`,get:e=>e.location?.region?.code,set:(e,t)=>{e.location||={},e.location.region||(e.location.region={}),e.location.region.code=t},type:`string`},{header:`x-netloc8-region-name`,get:e=>e.location?.region?.name,set:(e,t)=>{e.location||={},e.location.region||(e.location.region={}),e.location.region.name=t},type:`string`},{header:`x-netloc8-district`,get:e=>e.location?.district,set:(e,t)=>{e.location||={},e.location.district=t},type:`string`},{header:`x-netloc8-city`,get:e=>e.location?.city,set:(e,t)=>{e.location||={},e.location.city=t},type:`string`},{header:`x-netloc8-postal-code`,get:e=>e.location?.postalCode,set:(e,t)=>{e.location||={},e.location.postalCode=t},type:`string`},{header:`x-netloc8-latitude`,get:e=>e.location?.coordinates?.latitude,set:(e,t)=>{e.location||={},e.location.coordinates||(e.location.coordinates={});let n=parseFloat(t);Number.isFinite(n)&&(e.location.coordinates.latitude=n)},type:`number`},{header:`x-netloc8-longitude`,get:e=>e.location?.coordinates?.longitude,set:(e,t)=>{e.location||={},e.location.coordinates||(e.location.coordinates={});let n=parseFloat(t);Number.isFinite(n)&&(e.location.coordinates.longitude=n)},type:`number`},{header:`x-netloc8-accuracy-radius`,get:e=>e.location?.coordinates?.accuracyRadius,set:(e,t)=>{e.location||={},e.location.coordinates||(e.location.coordinates={});let n=parseFloat(t);Number.isFinite(n)&&(e.location.coordinates.accuracyRadius=n)},type:`number`},{header:`x-netloc8-timezone`,get:e=>e.location?.timezone,set:(e,t)=>{e.location||={},e.location.timezone=t},type:`string`},{header:`x-netloc8-utc-offset`,get:e=>e.location?.utcOffset,set:(e,t)=>{e.location||={},e.location.utcOffset=t},type:`string`},{header:`x-netloc8-geo-confidence`,get:e=>e.location?.geoConfidence,set:(e,t)=>{let n=parseFloat(t);Number.isFinite(n)&&(e.location||={},e.location.geoConfidence=n)},type:`number`},{header:`x-netloc8-asn`,get:e=>e.network?.asn,set:(e,t)=>{e.network||={},e.network.asn=t},type:`string`},{header:`x-netloc8-asn-org`,get:e=>e.network?.organization,set:(e,t)=>{e.network||={},e.network.organization=t},type:`string`},{header:`x-netloc8-asn-domain`,get:e=>e.network?.domain,set:(e,t)=>{e.network||={},e.network.domain=t},type:`string`},{header:`x-netloc8-precision`,get:e=>e.meta?.precision,set:(e,t)=>{e.meta||={},e.meta.precision=t},type:`string`},{header:`x-netloc8-degraded`,get:e=>e.meta?.degraded,set:(e,t)=>{e.meta||={},e.meta.degraded=t===`true`},type:`boolean`},{header:`x-netloc8-timezone-from-client`,get:e=>e.location?.timezoneFromClient,set:(e,t)=>{e.location||={},e.location.timezoneFromClient=t===`true`},type:`boolean`}];function f(e,t){for(let n of d){let r=n.get(t);r!=null&&(n.type===`json`?e.set(n.header,encodeURIComponent(JSON.stringify(r))):e.set(n.header,encodeURIComponent(String(r))))}}function p(e){let t={};for(let n of d){let r=e.get(n.header);if(r!==null)try{let e=decodeURIComponent(r);n.set(t,e)}catch{}}return t}function m(p){return async m=>{let h=p?.apiKey??process.env.NETLOC8_API_KEY,g=p?.apiUrl??process.env.NETLOC8_API_URL,_=p?.timeout??1500,v=`@netloc8/nextjs/1.1.0`,y=new Headers(m.headers);for(let e of d)y.delete(e.header);let b;process.env.NODE_ENV!==`production`&&(b=p?.testIp??process.env.NETLOC8_TEST_IP),b||=i(m.headers);let x=m.cookies.get(t)?.value,S=c(x),C=S.location?.timezoneFromClient===!0&&S.query?.value===b?{timezone:S.location.timezone,timezoneFromClient:S.location.timezoneFromClient}:void 0,w=a(m.headers),T=S.query?.value!==b,E;if(process.env.NODE_ENV!==`production`&&!h&&T&&(w.location?.country?.code?console.warn(`[netloc8] No API key configured — using platform headers only (country-level).
|
|
2
|
+
Get a free key at https://netloc8.com for city-level geo.`):console.warn(`[netloc8] No API key configured and no platform geo headers detected.
|
|
3
|
+
A free API key enables faster geo responses. Get one at https://netloc8.com`)),b&&o(b)&&T)if(h){let e=await r(b,{apiKey:h,apiUrl:g,timeout:_,clientId:v});e&&(E=s(e,b))}else r(b,{apiUrl:g,timeout:100,clientId:v,allowAnonymous:!0}).catch(()=>{});let D=l({cookie:S.query?.value?S:void 0,platform:w,api:E,ip:b});C&&(D.location||={},D.location.timezone=C.timezone,D.location.timezoneFromClient=C.timezoneFromClient),f(y,D);let O;if(p?.handler){let e=new Request(m.nextUrl.toString(),{method:m.method??`GET`,headers:y,body:m.body,duplex:`half`});O=await p.handler(Object.assign(e,{nextUrl:m.nextUrl,cookies:m.cookies}),D)}let k=O??e.next({request:{headers:y}});return(!x||T)&&k.cookies.set(t,u(D),{path:n.path,httpOnly:n.httpOnly,secure:n.secure,sameSite:n.sameSite,maxAge:n.maxAge}),k}}function h(t){let{defaultLocale:n,localeMap:r,excludePaths:i=[]}=t,a=new Set(Object.values(r));return a.add(n),(t,o)=>{let s=t.nextUrl.pathname;for(let e of i)if(s.startsWith(e))return;let c=s.split(`/`).filter(Boolean)[0];if(c&&a.has(c))return;let l=o.location?.country?.code,u=l&&r[l]||n;if(u===n)return;let d=t.nextUrl.clone();return d.pathname=`/${u}${s}`,e.redirect(d,307)}}export{m as createProxy,p as readGeoHeaders,h as withGeoRedirect};
|
|
374
4
|
//# sourceMappingURL=proxy.mjs.map
|
package/dist/proxy.mjs.map
CHANGED
|
@@ -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/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// --- Header transport ---\n// These map nested Geo paths to/from x-netloc8-* request headers\n// for the proxy → Server Component transport layer.\n\ninterface HeaderEntry {\n header: string;\n get: (geo: Geo) => string | number | boolean | string[] | undefined;\n set: (geo: Geo, raw: string) => void;\n type: 'string' | 'number' | 'boolean' | 'json';\n}\n\nconst HEADER_ENTRIES: HeaderEntry[] = [\n {\n header: 'x-netloc8-ip',\n get: (g) => g.query?.value,\n set: (g, v) => { if (!g.query) g.query = {}; g.query.value = v; },\n type: 'string',\n },\n {\n header: 'x-netloc8-ip-version',\n get: (g) => g.query?.ipVersion,\n set: (g, v) => { const n = parseFloat(v); if (!Number.isFinite(n)) return; if (!g.query) g.query = {}; g.query.ipVersion = n; },\n type: 'number',\n },\n {\n header: 'x-netloc8-continent-code',\n get: (g) => g.location?.continent?.code,\n set: (g, v) => {\n if (!g.location) g.location = {};\n if (!g.location.continent) g.location.continent = {};\n g.location.continent.code = v;\n },\n type: 'string',\n },\n {\n header: 'x-netloc8-continent-name',\n get: (g) => g.location?.continent?.name,\n set: (g, v) => {\n if (!g.location) g.location = {};\n if (!g.location.continent) g.location.continent = {};\n g.location.continent.name = v;\n },\n type: 'string',\n },\n {\n header: 'x-netloc8-country-code',\n get: (g) => g.location?.country?.code,\n set: (g, v) => {\n if (!g.location) g.location = {};\n if (!g.location.country) g.location.country = {};\n g.location.country.code = v;\n },\n type: 'string',\n },\n {\n header: 'x-netloc8-country-name',\n get: (g) => g.location?.country?.name,\n set: (g, v) => {\n if (!g.location) g.location = {};\n if (!g.location.country) g.location.country = {};\n g.location.country.name = v;\n },\n type: 'string',\n },\n {\n header: 'x-netloc8-country-flag',\n get: (g) => g.location?.country?.flag,\n set: (g, v) => {\n if (!g.location) g.location = {};\n if (!g.location.country) g.location.country = {};\n g.location.country.flag = v;\n },\n type: 'string',\n },\n {\n header: 'x-netloc8-country-unions',\n get: (g) => g.location?.country?.unions,\n set: (g, v) => {\n if (!g.location) g.location = {};\n if (!g.location.country) g.location.country = {};\n try {\n const parsed = JSON.parse(v);\n if (Array.isArray(parsed)) {\n g.location.country.unions = parsed;\n }\n } catch {\n // Skip malformed JSON\n }\n },\n type: 'json',\n },\n {\n header: 'x-netloc8-region-code',\n get: (g) => g.location?.region?.code,\n set: (g, v) => {\n if (!g.location) g.location = {};\n if (!g.location.region) g.location.region = {};\n g.location.region.code = v;\n },\n type: 'string',\n },\n {\n header: 'x-netloc8-region-name',\n get: (g) => g.location?.region?.name,\n set: (g, v) => {\n if (!g.location) g.location = {};\n if (!g.location.region) g.location.region = {};\n g.location.region.name = v;\n },\n type: 'string',\n },\n {\n header: 'x-netloc8-district',\n get: (g) => g.location?.district,\n set: (g, v) => { if (!g.location) g.location = {}; g.location.district = v; },\n type: 'string',\n },\n {\n header: 'x-netloc8-city',\n get: (g) => g.location?.city,\n set: (g, v) => { if (!g.location) g.location = {}; g.location.city = v; },\n type: 'string',\n },\n {\n header: 'x-netloc8-postal-code',\n get: (g) => g.location?.postalCode,\n set: (g, v) => { if (!g.location) g.location = {}; g.location.postalCode = v; },\n type: 'string',\n },\n {\n header: 'x-netloc8-latitude',\n get: (g) => g.location?.coordinates?.latitude,\n set: (g, v) => {\n if (!g.location) g.location = {};\n if (!g.location.coordinates) g.location.coordinates = {};\n const n = parseFloat(v); if (!Number.isFinite(n)) return;\n g.location.coordinates.latitude = n;\n },\n type: 'number',\n },\n {\n header: 'x-netloc8-longitude',\n get: (g) => g.location?.coordinates?.longitude,\n set: (g, v) => {\n if (!g.location) g.location = {};\n if (!g.location.coordinates) g.location.coordinates = {};\n const n = parseFloat(v); if (!Number.isFinite(n)) return;\n g.location.coordinates.longitude = n;\n },\n type: 'number',\n },\n {\n header: 'x-netloc8-accuracy-radius',\n get: (g) => g.location?.coordinates?.accuracyRadius,\n set: (g, v) => {\n if (!g.location) g.location = {};\n if (!g.location.coordinates) g.location.coordinates = {};\n const n = parseFloat(v); if (!Number.isFinite(n)) return;\n g.location.coordinates.accuracyRadius = n;\n },\n type: 'number',\n },\n {\n header: 'x-netloc8-timezone',\n get: (g) => g.location?.timezone,\n set: (g, v) => { if (!g.location) g.location = {}; g.location.timezone = v; },\n type: 'string',\n },\n {\n header: 'x-netloc8-utc-offset',\n get: (g) => g.location?.utcOffset,\n set: (g, v) => { if (!g.location) g.location = {}; g.location.utcOffset = v; },\n type: 'string',\n },\n {\n header: 'x-netloc8-geo-confidence',\n get: (g) => g.location?.geoConfidence,\n set: (g, v) => { const n = parseFloat(v); if (!Number.isFinite(n)) return; if (!g.location) g.location = {}; g.location.geoConfidence = n; },\n type: 'number',\n },\n {\n header: 'x-netloc8-asn',\n get: (g) => g.network?.asn,\n set: (g, v) => { if (!g.network) g.network = {}; g.network.asn = v; },\n type: 'string',\n },\n {\n header: 'x-netloc8-asn-org',\n get: (g) => g.network?.organization,\n set: (g, v) => { if (!g.network) g.network = {}; g.network.organization = v; },\n type: 'string',\n },\n {\n header: 'x-netloc8-asn-domain',\n get: (g) => g.network?.domain,\n set: (g, v) => { if (!g.network) g.network = {}; g.network.domain = v; },\n type: 'string',\n },\n {\n header: 'x-netloc8-precision',\n get: (g) => g.meta?.precision,\n set: (g, v) => { if (!g.meta) g.meta = {}; g.meta.precision = v; },\n type: 'string',\n },\n {\n header: 'x-netloc8-degraded',\n get: (g) => g.meta?.degraded,\n set: (g, v) => { if (!g.meta) g.meta = {}; g.meta.degraded = v === 'true'; },\n type: 'boolean',\n },\n {\n header: 'x-netloc8-timezone-from-client',\n get: (g) => g.location?.timezoneFromClient,\n set: (g, v) => { if (!g.location) g.location = {}; g.location.timezoneFromClient = v === 'true'; },\n type: 'boolean',\n },\n];\n\n/**\n * Set x-netloc8-* request headers from a Geo object.\n */\nfunction setGeoHeaders(requestHeaders: Headers, geo: Geo): void {\n for (const entry of HEADER_ENTRIES) {\n const value = entry.get(geo);\n if (value !== undefined && value !== null) {\n if (entry.type === 'json') {\n requestHeaders.set(entry.header, encodeURIComponent(JSON.stringify(value)));\n } else {\n requestHeaders.set(entry.header, encodeURIComponent(String(value)));\n }\n }\n }\n}\n\n/**\n * Read x-netloc8-* request headers back into a Geo object.\n * Used by server.ts to reconstruct Geo on the server side.\n */\nexport function readGeoHeaders(headers: Headers): Geo {\n const geo: Geo = {};\n\n for (const entry of HEADER_ENTRIES) {\n const raw = headers.get(entry.header);\n if (raw === null) {\n continue;\n }\n\n try {\n const decoded = decodeURIComponent(raw);\n entry.set(geo, decoded);\n } catch {\n // Skip this header if decodeURIComponent throws\n }\n }\n\n return geo;\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 entry of HEADER_ENTRIES) {\n requestHeaders.delete(entry.header);\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.location?.timezoneFromClient === true &&\n cookieGeo.query?.value === clientIp\n ) ? {\n timezone: cookieGeo.location.timezone,\n timezoneFromClient: cookieGeo.location.timezoneFromClient,\n } : 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.location?.country?.code && !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.query?.value ? cookieGeo : undefined,\n platform: platformGeo,\n api: apiGeo,\n ip: clientIp,\n });\n\n // Apply trusted cookie timezone if available\n if (cookieTimezone) {\n if (!geo.location) geo.location = {};\n geo.location.timezone = cookieTimezone.timezone;\n geo.location.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.query?.value !== 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 countryCode = geo.location?.country?.code;\n const locale = (countryCode && localeMap[countryCode]) || 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// --- Header transport ---\n// These map nested Geo paths to/from x-netloc8-* request headers\n// for the proxy → Server Component transport layer.\n\ninterface HeaderEntry {\n header: string;\n get: (geo: Geo) => string | number | boolean | string[] | undefined;\n set: (geo: Geo, raw: string) => void;\n type: 'string' | 'number' | 'boolean' | 'json';\n}\n\nconst HEADER_ENTRIES: HeaderEntry[] = [\n {\n header: 'x-netloc8-ip',\n get: (g) => g.query?.value,\n set: (g, v) => { if (!g.query) g.query = {}; g.query.value = v; },\n type: 'string',\n },\n {\n header: 'x-netloc8-ip-version',\n get: (g) => g.query?.ipVersion,\n set: (g, v) => { const n = parseFloat(v); if (!Number.isFinite(n)) return; if (!g.query) g.query = {}; g.query.ipVersion = n; },\n type: 'number',\n },\n {\n header: 'x-netloc8-continent-code',\n get: (g) => g.location?.continent?.code,\n set: (g, v) => {\n if (!g.location) g.location = {};\n if (!g.location.continent) g.location.continent = {};\n g.location.continent.code = v;\n },\n type: 'string',\n },\n {\n header: 'x-netloc8-continent-name',\n get: (g) => g.location?.continent?.name,\n set: (g, v) => {\n if (!g.location) g.location = {};\n if (!g.location.continent) g.location.continent = {};\n g.location.continent.name = v;\n },\n type: 'string',\n },\n {\n header: 'x-netloc8-country-code',\n get: (g) => g.location?.country?.code,\n set: (g, v) => {\n if (!g.location) g.location = {};\n if (!g.location.country) g.location.country = {};\n g.location.country.code = v;\n },\n type: 'string',\n },\n {\n header: 'x-netloc8-country-name',\n get: (g) => g.location?.country?.name,\n set: (g, v) => {\n if (!g.location) g.location = {};\n if (!g.location.country) g.location.country = {};\n g.location.country.name = v;\n },\n type: 'string',\n },\n {\n header: 'x-netloc8-country-flag',\n get: (g) => g.location?.country?.flag,\n set: (g, v) => {\n if (!g.location) g.location = {};\n if (!g.location.country) g.location.country = {};\n g.location.country.flag = v;\n },\n type: 'string',\n },\n {\n header: 'x-netloc8-country-unions',\n get: (g) => g.location?.country?.unions,\n set: (g, v) => {\n if (!g.location) g.location = {};\n if (!g.location.country) g.location.country = {};\n try {\n const parsed = JSON.parse(v);\n if (Array.isArray(parsed)) {\n g.location.country.unions = parsed;\n }\n } catch {\n // Skip malformed JSON\n }\n },\n type: 'json',\n },\n {\n header: 'x-netloc8-region-code',\n get: (g) => g.location?.region?.code,\n set: (g, v) => {\n if (!g.location) g.location = {};\n if (!g.location.region) g.location.region = {};\n g.location.region.code = v;\n },\n type: 'string',\n },\n {\n header: 'x-netloc8-region-name',\n get: (g) => g.location?.region?.name,\n set: (g, v) => {\n if (!g.location) g.location = {};\n if (!g.location.region) g.location.region = {};\n g.location.region.name = v;\n },\n type: 'string',\n },\n {\n header: 'x-netloc8-district',\n get: (g) => g.location?.district,\n set: (g, v) => { if (!g.location) g.location = {}; g.location.district = v; },\n type: 'string',\n },\n {\n header: 'x-netloc8-city',\n get: (g) => g.location?.city,\n set: (g, v) => { if (!g.location) g.location = {}; g.location.city = v; },\n type: 'string',\n },\n {\n header: 'x-netloc8-postal-code',\n get: (g) => g.location?.postalCode,\n set: (g, v) => { if (!g.location) g.location = {}; g.location.postalCode = v; },\n type: 'string',\n },\n {\n header: 'x-netloc8-latitude',\n get: (g) => g.location?.coordinates?.latitude,\n set: (g, v) => {\n if (!g.location) g.location = {};\n if (!g.location.coordinates) g.location.coordinates = {};\n const n = parseFloat(v); if (!Number.isFinite(n)) return;\n g.location.coordinates.latitude = n;\n },\n type: 'number',\n },\n {\n header: 'x-netloc8-longitude',\n get: (g) => g.location?.coordinates?.longitude,\n set: (g, v) => {\n if (!g.location) g.location = {};\n if (!g.location.coordinates) g.location.coordinates = {};\n const n = parseFloat(v); if (!Number.isFinite(n)) return;\n g.location.coordinates.longitude = n;\n },\n type: 'number',\n },\n {\n header: 'x-netloc8-accuracy-radius',\n get: (g) => g.location?.coordinates?.accuracyRadius,\n set: (g, v) => {\n if (!g.location) g.location = {};\n if (!g.location.coordinates) g.location.coordinates = {};\n const n = parseFloat(v); if (!Number.isFinite(n)) return;\n g.location.coordinates.accuracyRadius = n;\n },\n type: 'number',\n },\n {\n header: 'x-netloc8-timezone',\n get: (g) => g.location?.timezone,\n set: (g, v) => { if (!g.location) g.location = {}; g.location.timezone = v; },\n type: 'string',\n },\n {\n header: 'x-netloc8-utc-offset',\n get: (g) => g.location?.utcOffset,\n set: (g, v) => { if (!g.location) g.location = {}; g.location.utcOffset = v; },\n type: 'string',\n },\n {\n header: 'x-netloc8-geo-confidence',\n get: (g) => g.location?.geoConfidence,\n set: (g, v) => { const n = parseFloat(v); if (!Number.isFinite(n)) return; if (!g.location) g.location = {}; g.location.geoConfidence = n; },\n type: 'number',\n },\n {\n header: 'x-netloc8-asn',\n get: (g) => g.network?.asn,\n set: (g, v) => { if (!g.network) g.network = {}; g.network.asn = v; },\n type: 'string',\n },\n {\n header: 'x-netloc8-asn-org',\n get: (g) => g.network?.organization,\n set: (g, v) => { if (!g.network) g.network = {}; g.network.organization = v; },\n type: 'string',\n },\n {\n header: 'x-netloc8-asn-domain',\n get: (g) => g.network?.domain,\n set: (g, v) => { if (!g.network) g.network = {}; g.network.domain = v; },\n type: 'string',\n },\n {\n header: 'x-netloc8-precision',\n get: (g) => g.meta?.precision,\n set: (g, v) => { if (!g.meta) g.meta = {}; g.meta.precision = v; },\n type: 'string',\n },\n {\n header: 'x-netloc8-degraded',\n get: (g) => g.meta?.degraded,\n set: (g, v) => { if (!g.meta) g.meta = {}; g.meta.degraded = v === 'true'; },\n type: 'boolean',\n },\n {\n header: 'x-netloc8-timezone-from-client',\n get: (g) => g.location?.timezoneFromClient,\n set: (g, v) => { if (!g.location) g.location = {}; g.location.timezoneFromClient = v === 'true'; },\n type: 'boolean',\n },\n];\n\n/**\n * Set x-netloc8-* request headers from a Geo object.\n */\nfunction setGeoHeaders(requestHeaders: Headers, geo: Geo): void {\n for (const entry of HEADER_ENTRIES) {\n const value = entry.get(geo);\n if (value !== undefined && value !== null) {\n if (entry.type === 'json') {\n requestHeaders.set(entry.header, encodeURIComponent(JSON.stringify(value)));\n } else {\n requestHeaders.set(entry.header, encodeURIComponent(String(value)));\n }\n }\n }\n}\n\n/**\n * Read x-netloc8-* request headers back into a Geo object.\n * Used by server.ts to reconstruct Geo on the server side.\n */\nexport function readGeoHeaders(headers: Headers): Geo {\n const geo: Geo = {};\n\n for (const entry of HEADER_ENTRIES) {\n const raw = headers.get(entry.header);\n if (raw === null) {\n continue;\n }\n\n try {\n const decoded = decodeURIComponent(raw);\n entry.set(geo, decoded);\n } catch {\n // Skip this header if decodeURIComponent throws\n }\n }\n\n return geo;\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 const clientId = typeof __PKG_NAME__ !== 'undefined' && typeof __PKG_VERSION__ !== 'undefined'\n ? `${__PKG_NAME__}/${__PKG_VERSION__}`\n : undefined;\n\n // Security: Remove any incoming spoofed headers\n const requestHeaders = new Headers(request.headers);\n for (const entry of HEADER_ENTRIES) {\n requestHeaders.delete(entry.header);\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.location?.timezoneFromClient === true &&\n cookieGeo.query?.value === clientIp\n ) ? {\n timezone: cookieGeo.location.timezone,\n timezoneFromClient: cookieGeo.location.timezoneFromClient,\n } : 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 // With API key: await full enrichment (city, coords, ASN, etc.).\n // Without key: attempt lookup with a short timeout to avoid\n // blocking the page — fall back to platform headers if it\n // doesn't complete in time.\n const ipChanged = cookieGeo.query?.value !== clientIp;\n let apiGeo: Geo | undefined;\n\n // Dev-mode warning: only on new visitors to avoid console spam\n if (process.env.NODE_ENV !== 'production' && !apiKey && ipChanged) {\n if (platformGeo.location?.country?.code) {\n console.warn(\n '[netloc8] No API key configured \\u2014 using platform headers only (country-level).\\n' +\n ' Get a free key at https://netloc8.com for city-level geo.'\n );\n } else {\n console.warn(\n '[netloc8] No API key configured and no platform geo headers detected.\\n' +\n ' A free API key enables faster geo responses. Get one at https://netloc8.com'\n );\n }\n }\n\n if (clientIp && isPublicIp(clientIp) && ipChanged) {\n if (apiKey) {\n const raw = await fetchGeo(clientIp, { apiKey, apiUrl, timeout, clientId });\n if (raw) {\n apiGeo = normalizeApiResponse(raw, clientIp);\n }\n } else {\n // No API key: attempt geo lookup with a short timeout.\n // Falls back to platform headers if the request doesn't complete.\n fetchGeo(clientIp, {\n apiUrl, timeout: 100, clientId, allowAnonymous: true,\n }).catch(() => {});\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.query?.value ? cookieGeo : undefined,\n platform: platformGeo,\n api: apiGeo,\n ip: clientIp,\n });\n\n // Apply trusted cookie timezone if available\n if (cookieTimezone) {\n if (!geo.location) geo.location = {};\n geo.location.timezone = cookieTimezone.timezone;\n geo.location.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 || ipChanged) {\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 countryCode = geo.location?.country?.code;\n const locale = (countryCode && localeMap[countryCode]) || 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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"}
|
package/dist/server.mjs
CHANGED
|
@@ -1,34 +1,2 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { DEFAULT_API_URL, fetchTimezone } from "@netloc8/core";
|
|
3
|
-
import { headers } from "next/headers";
|
|
4
|
-
//#region src/server.ts
|
|
5
|
-
/**
|
|
6
|
-
* Read geo data in a Server Component, Route Handler, or Server Action.
|
|
7
|
-
*
|
|
8
|
-
* Reads x-netloc8-* headers set by `createProxy()` and reconstructs a
|
|
9
|
-
* full Geo object.
|
|
10
|
-
*/
|
|
11
|
-
async function getGeo() {
|
|
12
|
-
return readGeoHeaders(await headers());
|
|
13
|
-
}
|
|
14
|
-
/**
|
|
15
|
-
* Read timezone in a Server Component, Route Handler, or Server Action.
|
|
16
|
-
*
|
|
17
|
-
* 1. Reads the timezone from the geo headers set by the proxy.
|
|
18
|
-
* 2. If empty, fetches from the API using the request IP.
|
|
19
|
-
*/
|
|
20
|
-
async function getTimezone() {
|
|
21
|
-
const geo = await getGeo();
|
|
22
|
-
const timezone = geo.location?.timezone;
|
|
23
|
-
if (timezone) return timezone;
|
|
24
|
-
const ip = geo.query?.value;
|
|
25
|
-
if (ip) return await fetchTimezone(ip, {
|
|
26
|
-
apiUrl: process.env.NETLOC8_API_URL ?? DEFAULT_API_URL,
|
|
27
|
-
apiKey: process.env.NETLOC8_API_KEY
|
|
28
|
-
});
|
|
29
|
-
return null;
|
|
30
|
-
}
|
|
31
|
-
//#endregion
|
|
32
|
-
export { getGeo, getTimezone };
|
|
33
|
-
|
|
1
|
+
import{readGeoHeaders as e}from"./proxy.mjs";import{DEFAULT_API_URL as t,fetchTimezone as n}from"@netloc8/core";import{headers as r}from"next/headers";async function i(){return e(await r())}async function a(){let e=await i(),r=e.location?.timezone;if(r)return r;let a=e.query?.value;return a?await n(a,{apiUrl:process.env.NETLOC8_API_URL??t,apiKey:process.env.NETLOC8_API_KEY}):null}export{i as getGeo,a as getTimezone};
|
|
34
2
|
//# sourceMappingURL=server.mjs.map
|
package/dist/server.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.mjs","names":[],"sources":["../src/server.ts"],"sourcesContent":["import type { Geo } from '@netloc8/core';\nimport { headers } from 'next/headers';\nimport { DEFAULT_API_URL, fetchTimezone } from '@netloc8/core';\nimport { readGeoHeaders } from './proxy';\n\n/**\n * Read geo data in a Server Component, Route Handler, or Server Action.\n *\n * Reads x-netloc8-* headers set by `createProxy()` and reconstructs a\n * full Geo object.\n */\nexport async function getGeo(): Promise<Geo> {\n const headerStore = await headers();\n return readGeoHeaders(headerStore);\n}\n\n/**\n * Read timezone in a Server Component, Route Handler, or Server Action.\n *\n * 1. Reads the timezone from the geo headers set by the proxy.\n * 2. If empty, fetches from the API using the request IP.\n */\nexport async function getTimezone(): Promise<string | null> {\n const geo = await getGeo();\n const timezone = geo.location?.timezone;\n\n if (timezone) {\n return timezone;\n }\n\n // Fall back to API\n const ip = geo.query?.value;\n if (ip) {\n return await fetchTimezone(ip, {\n apiUrl: process.env.NETLOC8_API_URL ?? DEFAULT_API_URL,\n apiKey: process.env.NETLOC8_API_KEY,\n });\n }\n\n return null;\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"server.mjs","names":[],"sources":["../src/server.ts"],"sourcesContent":["import type { Geo } from '@netloc8/core';\nimport { headers } from 'next/headers';\nimport { DEFAULT_API_URL, fetchTimezone } from '@netloc8/core';\nimport { readGeoHeaders } from './proxy';\n\n/**\n * Read geo data in a Server Component, Route Handler, or Server Action.\n *\n * Reads x-netloc8-* headers set by `createProxy()` and reconstructs a\n * full Geo object.\n */\nexport async function getGeo(): Promise<Geo> {\n const headerStore = await headers();\n return readGeoHeaders(headerStore);\n}\n\n/**\n * Read timezone in a Server Component, Route Handler, or Server Action.\n *\n * 1. Reads the timezone from the geo headers set by the proxy.\n * 2. If empty, fetches from the API using the request IP.\n */\nexport async function getTimezone(): Promise<string | null> {\n const geo = await getGeo();\n const timezone = geo.location?.timezone;\n\n if (timezone) {\n return timezone;\n }\n\n // Fall back to API\n const ip = geo.query?.value;\n if (ip) {\n return await fetchTimezone(ip, {\n apiUrl: process.env.NETLOC8_API_URL ?? DEFAULT_API_URL,\n apiKey: process.env.NETLOC8_API_KEY,\n });\n }\n\n return null;\n}\n"],"mappings":"uJAWA,eAAsB,GAAuB,CAEzC,OAAO,EADa,MAAM,GAAS,CACD,CAStC,eAAsB,GAAsC,CACxD,IAAM,EAAM,MAAM,GAAQ,CACpB,EAAW,EAAI,UAAU,SAE/B,GAAI,EACA,OAAO,EAIX,IAAM,EAAK,EAAI,OAAO,MAQtB,OAPI,EACO,MAAM,EAAc,EAAI,CAC3B,OAAQ,QAAQ,IAAI,iBAAmB,EACvC,OAAQ,QAAQ,IAAI,gBACvB,CAAC,CAGC"}
|
package/package.json
CHANGED