@poly-x/next 0.1.0-alpha.13 → 0.1.0-alpha.15

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/proxy.cjs CHANGED
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- require("./server-session-BpRaJCz9.cjs");
2
+ require("./server-session-QJLA_Tz-.cjs");
3
3
  let next_server = require("next/server");
4
4
  //#region src/proxy.ts
5
5
  /**
package/dist/proxy.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import "./server-session-DedPwl4b.mjs";
1
+ import "./server-session-CjP44zFC.mjs";
2
2
  import { NextResponse } from "next/server";
3
3
  //#region src/proxy.ts
4
4
  /**
@@ -85,11 +85,12 @@ var ServerSession = class {
85
85
  this.cookieName = options.cookieName ?? "polyx_session";
86
86
  this.secure = options.secure ?? true;
87
87
  }
88
- async establishFromCode(code, codeVerifier, redirectUri) {
88
+ async establishFromCode(code, codeVerifier, redirectUri, forwardedHeaders) {
89
89
  const stored = await this.authClient.exchangeCode({
90
90
  code,
91
91
  codeVerifier,
92
- redirectUri
92
+ redirectUri,
93
+ forwardedHeaders
93
94
  });
94
95
  await this.write(stored);
95
96
  return stored;
@@ -85,11 +85,12 @@ var ServerSession = class {
85
85
  this.cookieName = options.cookieName ?? "polyx_session";
86
86
  this.secure = options.secure ?? true;
87
87
  }
88
- async establishFromCode(code, codeVerifier, redirectUri) {
88
+ async establishFromCode(code, codeVerifier, redirectUri, forwardedHeaders) {
89
89
  const stored = await this.authClient.exchangeCode({
90
90
  code,
91
91
  codeVerifier,
92
- redirectUri
92
+ redirectUri,
93
+ forwardedHeaders
93
94
  });
94
95
  await this.write(stored);
95
96
  return stored;
package/dist/server.cjs CHANGED
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_server_session = require("./server-session-BpRaJCz9.cjs");
2
+ const require_server_session = require("./server-session-QJLA_Tz-.cjs");
3
3
  let _poly_x_core = require("@poly-x/core");
4
4
  let next_headers = require("next/headers");
5
5
  //#region src/config.ts
@@ -72,6 +72,65 @@ function resolveCookieSecure(request, config) {
72
72
  return true;
73
73
  }
74
74
  }
75
+ /** Browser request headers the platform reads for session device attribution. */
76
+ const FORWARDED_DEVICE_HEADERS = [
77
+ "user-agent",
78
+ "accept-language",
79
+ "sec-ch-ua",
80
+ "sec-ch-ua-platform",
81
+ "sec-ch-ua-mobile",
82
+ "sec-ch-ua-model"
83
+ ];
84
+ /**
85
+ * The end user's IP as observed by THIS BFF's own infrastructure — the left-most
86
+ * entry of the inbound `x-forwarded-for` (what the platform/proxy in front of the
87
+ * app set), falling back to `x-real-ip`. Returns undefined on localhost/dev where
88
+ * neither is present, letting the platform fall back to its own resolution rather
89
+ * than recording a blank. (This trusts the infra-set forwarded chain, not a value
90
+ * the browser could set on the same-origin BFF request.)
91
+ */
92
+ function resolveClientIp(request) {
93
+ const first = request.headers.get("x-forwarded-for")?.split(",")[0]?.trim();
94
+ if (first) return first;
95
+ return request.headers.get("x-real-ip")?.trim() || void 0;
96
+ }
97
+ /**
98
+ * Curate the browser context to forward on the session-creating token exchange.
99
+ * In a BFF deployment the outbound call to poly-auth is made by Node, so without
100
+ * this the platform attributes the session to the server ("node"/Unknown UA, the
101
+ * server's IP). We forward a fixed allowlist of the browser's own request headers
102
+ * plus the observed client IP (as `x-forwarded-for`). Only headers actually
103
+ * present are forwarded — never blank values that would clobber the platform's
104
+ * own fallback resolution.
105
+ */
106
+ function collectForwardedDeviceHeaders(request) {
107
+ const forwarded = {};
108
+ for (const name of FORWARDED_DEVICE_HEADERS) {
109
+ const value = request.headers.get(name);
110
+ if (value) forwarded[name] = value;
111
+ }
112
+ const clientIp = resolveClientIp(request);
113
+ if (clientIp) forwarded["x-forwarded-for"] = clientIp;
114
+ return forwarded;
115
+ }
116
+ /**
117
+ * Build the forwarded precise-location headers (`x-polyx-geo-*`) from the browser's login
118
+ * body (v6, FR-LOC-7). The browser captures a consented position client-side and posts it;
119
+ * the platform reads these on the token exchange. Only well-formed values are forwarded, and
120
+ * the consent state rides even when coordinates are absent (denied/timeout → coarse fallback).
121
+ */
122
+ function geoHeadersFromBody(geo) {
123
+ const headers = {};
124
+ if (!geo || typeof geo !== "object") return headers;
125
+ const g = geo;
126
+ const lat = Number(g.latitude);
127
+ const lon = Number(g.longitude);
128
+ if (typeof g.latitude === "string" && Number.isFinite(lat) && lat >= -90 && lat <= 90) headers["x-polyx-geo-lat"] = g.latitude;
129
+ if (typeof g.longitude === "string" && Number.isFinite(lon) && lon >= -180 && lon <= 180) headers["x-polyx-geo-lon"] = g.longitude;
130
+ if (typeof g.accuracyMeters === "number" && Number.isFinite(g.accuracyMeters) && g.accuracyMeters >= 0) headers["x-polyx-geo-accuracy"] = String(g.accuracyMeters);
131
+ if (g.consent === "granted" || g.consent === "denied" || g.consent === "revoked") headers["x-polyx-geo-consent"] = g.consent;
132
+ return headers;
133
+ }
75
134
  /** Read the current session — safe in Server Components (no cookie write). */
76
135
  async function auth(overrides) {
77
136
  const config = resolveNextConfig(overrides);
@@ -128,6 +187,7 @@ function createAuthHandlers(handlersConfig = {}) {
128
187
  } catch {
129
188
  return Response.json({ status: "invalid_request" }, { status: 400 });
130
189
  }
190
+ const geoHeaders = geoHeadersFromBody(payload.geo);
131
191
  const email = typeof payload.email === "string" ? payload.email : "";
132
192
  const password = typeof payload.password === "string" ? payload.password : "";
133
193
  if (!email || !password) return Response.json({ status: "invalid_request" }, { status: 400 });
@@ -149,7 +209,10 @@ function createAuthHandlers(handlersConfig = {}) {
149
209
  });
150
210
  switch (outcome.type) {
151
211
  case "authorized":
152
- await newSession(adapt(await (0, next_headers.cookies)()), config, secure).establishFromCode(outcome.code, verifier, redirectUri);
212
+ await newSession(adapt(await (0, next_headers.cookies)()), config, secure).establishFromCode(outcome.code, verifier, redirectUri, {
213
+ ...collectForwardedDeviceHeaders(request),
214
+ ...geoHeaders
215
+ });
153
216
  return Response.json({
154
217
  status: "success",
155
218
  redirectTo: afterSignInPath
@@ -249,7 +312,7 @@ function createAuthHandlers(handlersConfig = {}) {
249
312
  if (!code || !state || !raw) return Response.redirect(home, 302);
250
313
  const tx = JSON.parse(raw);
251
314
  if (tx.state !== state) return Response.redirect(home, 302);
252
- await newSession(adapt(store), config, secure).establishFromCode(code, tx.verifier, tx.redirectUri);
315
+ await newSession(adapt(store), config, secure).establishFromCode(code, tx.verifier, tx.redirectUri, collectForwardedDeviceHeaders(request));
253
316
  return Response.redirect(home, 302);
254
317
  },
255
318
  async refresh(request) {
package/dist/server.d.cts CHANGED
@@ -58,7 +58,7 @@ declare class ServerSession {
58
58
  private readonly cookieName;
59
59
  private readonly secure;
60
60
  constructor(options: ServerSessionOptions);
61
- establishFromCode(code: string, codeVerifier: string, redirectUri: string): Promise<StoredSession>;
61
+ establishFromCode(code: string, codeVerifier: string, redirectUri: string, forwardedHeaders?: Record<string, string>): Promise<StoredSession>;
62
62
  read(): Promise<StoredSession | null>;
63
63
  /** Rotate the tokens; clears the cookie (returns null) on a terminal refresh failure. */
64
64
  refresh(): Promise<StoredSession | null>;
package/dist/server.d.mts CHANGED
@@ -58,7 +58,7 @@ declare class ServerSession {
58
58
  private readonly cookieName;
59
59
  private readonly secure;
60
60
  constructor(options: ServerSessionOptions);
61
- establishFromCode(code: string, codeVerifier: string, redirectUri: string): Promise<StoredSession>;
61
+ establishFromCode(code: string, codeVerifier: string, redirectUri: string, forwardedHeaders?: Record<string, string>): Promise<StoredSession>;
62
62
  read(): Promise<StoredSession | null>;
63
63
  /** Rotate the tokens; clears the cookie (returns null) on a terminal refresh failure. */
64
64
  refresh(): Promise<StoredSession | null>;
package/dist/server.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { a as sealSession, i as openSession, n as ServerSession, r as toAuthContext, t as DEFAULT_SESSION_COOKIE } from "./server-session-DedPwl4b.mjs";
1
+ import { a as sealSession, i as openSession, n as ServerSession, r as toAuthContext, t as DEFAULT_SESSION_COOKIE } from "./server-session-CjP44zFC.mjs";
2
2
  import { AuthClient, FetchTransport, generatePkce, randomState, resolveConfig } from "@poly-x/core";
3
3
  import { cookies } from "next/headers";
4
4
  //#region src/config.ts
@@ -71,6 +71,65 @@ function resolveCookieSecure(request, config) {
71
71
  return true;
72
72
  }
73
73
  }
74
+ /** Browser request headers the platform reads for session device attribution. */
75
+ const FORWARDED_DEVICE_HEADERS = [
76
+ "user-agent",
77
+ "accept-language",
78
+ "sec-ch-ua",
79
+ "sec-ch-ua-platform",
80
+ "sec-ch-ua-mobile",
81
+ "sec-ch-ua-model"
82
+ ];
83
+ /**
84
+ * The end user's IP as observed by THIS BFF's own infrastructure — the left-most
85
+ * entry of the inbound `x-forwarded-for` (what the platform/proxy in front of the
86
+ * app set), falling back to `x-real-ip`. Returns undefined on localhost/dev where
87
+ * neither is present, letting the platform fall back to its own resolution rather
88
+ * than recording a blank. (This trusts the infra-set forwarded chain, not a value
89
+ * the browser could set on the same-origin BFF request.)
90
+ */
91
+ function resolveClientIp(request) {
92
+ const first = request.headers.get("x-forwarded-for")?.split(",")[0]?.trim();
93
+ if (first) return first;
94
+ return request.headers.get("x-real-ip")?.trim() || void 0;
95
+ }
96
+ /**
97
+ * Curate the browser context to forward on the session-creating token exchange.
98
+ * In a BFF deployment the outbound call to poly-auth is made by Node, so without
99
+ * this the platform attributes the session to the server ("node"/Unknown UA, the
100
+ * server's IP). We forward a fixed allowlist of the browser's own request headers
101
+ * plus the observed client IP (as `x-forwarded-for`). Only headers actually
102
+ * present are forwarded — never blank values that would clobber the platform's
103
+ * own fallback resolution.
104
+ */
105
+ function collectForwardedDeviceHeaders(request) {
106
+ const forwarded = {};
107
+ for (const name of FORWARDED_DEVICE_HEADERS) {
108
+ const value = request.headers.get(name);
109
+ if (value) forwarded[name] = value;
110
+ }
111
+ const clientIp = resolveClientIp(request);
112
+ if (clientIp) forwarded["x-forwarded-for"] = clientIp;
113
+ return forwarded;
114
+ }
115
+ /**
116
+ * Build the forwarded precise-location headers (`x-polyx-geo-*`) from the browser's login
117
+ * body (v6, FR-LOC-7). The browser captures a consented position client-side and posts it;
118
+ * the platform reads these on the token exchange. Only well-formed values are forwarded, and
119
+ * the consent state rides even when coordinates are absent (denied/timeout → coarse fallback).
120
+ */
121
+ function geoHeadersFromBody(geo) {
122
+ const headers = {};
123
+ if (!geo || typeof geo !== "object") return headers;
124
+ const g = geo;
125
+ const lat = Number(g.latitude);
126
+ const lon = Number(g.longitude);
127
+ if (typeof g.latitude === "string" && Number.isFinite(lat) && lat >= -90 && lat <= 90) headers["x-polyx-geo-lat"] = g.latitude;
128
+ if (typeof g.longitude === "string" && Number.isFinite(lon) && lon >= -180 && lon <= 180) headers["x-polyx-geo-lon"] = g.longitude;
129
+ if (typeof g.accuracyMeters === "number" && Number.isFinite(g.accuracyMeters) && g.accuracyMeters >= 0) headers["x-polyx-geo-accuracy"] = String(g.accuracyMeters);
130
+ if (g.consent === "granted" || g.consent === "denied" || g.consent === "revoked") headers["x-polyx-geo-consent"] = g.consent;
131
+ return headers;
132
+ }
74
133
  /** Read the current session — safe in Server Components (no cookie write). */
75
134
  async function auth(overrides) {
76
135
  const config = resolveNextConfig(overrides);
@@ -127,6 +186,7 @@ function createAuthHandlers(handlersConfig = {}) {
127
186
  } catch {
128
187
  return Response.json({ status: "invalid_request" }, { status: 400 });
129
188
  }
189
+ const geoHeaders = geoHeadersFromBody(payload.geo);
130
190
  const email = typeof payload.email === "string" ? payload.email : "";
131
191
  const password = typeof payload.password === "string" ? payload.password : "";
132
192
  if (!email || !password) return Response.json({ status: "invalid_request" }, { status: 400 });
@@ -148,7 +208,10 @@ function createAuthHandlers(handlersConfig = {}) {
148
208
  });
149
209
  switch (outcome.type) {
150
210
  case "authorized":
151
- await newSession(adapt(await cookies()), config, secure).establishFromCode(outcome.code, verifier, redirectUri);
211
+ await newSession(adapt(await cookies()), config, secure).establishFromCode(outcome.code, verifier, redirectUri, {
212
+ ...collectForwardedDeviceHeaders(request),
213
+ ...geoHeaders
214
+ });
152
215
  return Response.json({
153
216
  status: "success",
154
217
  redirectTo: afterSignInPath
@@ -248,7 +311,7 @@ function createAuthHandlers(handlersConfig = {}) {
248
311
  if (!code || !state || !raw) return Response.redirect(home, 302);
249
312
  const tx = JSON.parse(raw);
250
313
  if (tx.state !== state) return Response.redirect(home, 302);
251
- await newSession(adapt(store), config, secure).establishFromCode(code, tx.verifier, tx.redirectUri);
314
+ await newSession(adapt(store), config, secure).establishFromCode(code, tx.verifier, tx.redirectUri, collectForwardedDeviceHeaders(request));
252
315
  return Response.redirect(home, 302);
253
316
  },
254
317
  async refresh(request) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@poly-x/next",
3
- "version": "0.1.0-alpha.13",
3
+ "version": "0.1.0-alpha.15",
4
4
  "description": "PolyX SDK for Next.js App Router - components plus server helpers",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -47,8 +47,8 @@
47
47
  "module": "./dist/index.mjs",
48
48
  "types": "./dist/index.d.cts",
49
49
  "dependencies": {
50
- "@poly-x/core": "0.1.0-alpha.13",
51
- "@poly-x/react": "0.1.0-alpha.13"
50
+ "@poly-x/react": "0.1.0-alpha.15",
51
+ "@poly-x/core": "0.1.0-alpha.15"
52
52
  },
53
53
  "peerDependencies": {
54
54
  "next": ">=14",