@poly-x/next 0.1.0-alpha.12 → 0.1.0-alpha.14

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,47 @@ 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
+ }
75
116
  /** Read the current session — safe in Server Components (no cookie write). */
76
117
  async function auth(overrides) {
77
118
  const config = resolveNextConfig(overrides);
@@ -149,7 +190,7 @@ function createAuthHandlers(handlersConfig = {}) {
149
190
  });
150
191
  switch (outcome.type) {
151
192
  case "authorized":
152
- await newSession(adapt(await (0, next_headers.cookies)()), config, secure).establishFromCode(outcome.code, verifier, redirectUri);
193
+ await newSession(adapt(await (0, next_headers.cookies)()), config, secure).establishFromCode(outcome.code, verifier, redirectUri, collectForwardedDeviceHeaders(request));
153
194
  return Response.json({
154
195
  status: "success",
155
196
  redirectTo: afterSignInPath
@@ -249,7 +290,7 @@ function createAuthHandlers(handlersConfig = {}) {
249
290
  if (!code || !state || !raw) return Response.redirect(home, 302);
250
291
  const tx = JSON.parse(raw);
251
292
  if (tx.state !== state) return Response.redirect(home, 302);
252
- await newSession(adapt(store), config, secure).establishFromCode(code, tx.verifier, tx.redirectUri);
293
+ await newSession(adapt(store), config, secure).establishFromCode(code, tx.verifier, tx.redirectUri, collectForwardedDeviceHeaders(request));
253
294
  return Response.redirect(home, 302);
254
295
  },
255
296
  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,47 @@ 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
+ }
74
115
  /** Read the current session — safe in Server Components (no cookie write). */
75
116
  async function auth(overrides) {
76
117
  const config = resolveNextConfig(overrides);
@@ -148,7 +189,7 @@ function createAuthHandlers(handlersConfig = {}) {
148
189
  });
149
190
  switch (outcome.type) {
150
191
  case "authorized":
151
- await newSession(adapt(await cookies()), config, secure).establishFromCode(outcome.code, verifier, redirectUri);
192
+ await newSession(adapt(await cookies()), config, secure).establishFromCode(outcome.code, verifier, redirectUri, collectForwardedDeviceHeaders(request));
152
193
  return Response.json({
153
194
  status: "success",
154
195
  redirectTo: afterSignInPath
@@ -248,7 +289,7 @@ function createAuthHandlers(handlersConfig = {}) {
248
289
  if (!code || !state || !raw) return Response.redirect(home, 302);
249
290
  const tx = JSON.parse(raw);
250
291
  if (tx.state !== state) return Response.redirect(home, 302);
251
- await newSession(adapt(store), config, secure).establishFromCode(code, tx.verifier, tx.redirectUri);
292
+ await newSession(adapt(store), config, secure).establishFromCode(code, tx.verifier, tx.redirectUri, collectForwardedDeviceHeaders(request));
252
293
  return Response.redirect(home, 302);
253
294
  },
254
295
  async refresh(request) {
package/dist/styles.css CHANGED
@@ -10,8 +10,33 @@
10
10
  * (`:root`, a wrapper div, …) — that is the supported theming surface. Dark mode
11
11
  * follows the OS by default and defers to an explicit `.dark` / `[data-theme]`
12
12
  * on a host ancestor when one is present.
13
+ *
14
+ * ---------------------------------------------------------------------------
15
+ * Cascade: everything here lives in the `polyx` layer, and that is load-bearing.
16
+ *
17
+ * Unlayered CSS beats layered CSS outright — not by specificity, but by rank; no
18
+ * import order or selector weight changes it. Shipping these rules unlayered
19
+ * therefore beat every Tailwind utility a host wrote (Tailwind emits utilities
20
+ * into `@layer utilities`), so `className`/`triggerClassName` silently did
21
+ * nothing and hosts were pushed toward `!important`. Layering hands that control
22
+ * back: a host's utilities and unlayered CSS both outrank us now.
23
+ *
24
+ * The statement below fixes where `polyx` sits. It must land AFTER `base` —
25
+ * Tailwind's preflight puts `*, ::before, ::after { border: 0 solid }` there, and
26
+ * that would erase our own borders if we ranked lower — and BEFORE `utilities`,
27
+ * so host utilities still win. Naming Tailwind's layers costs nothing when the
28
+ * host doesn't use Tailwind: the names simply resolve to empty layers.
29
+ *
30
+ * Layer order is set by first declaration, so this only holds if our stylesheet
31
+ * is imported BEFORE the host's Tailwind entry. Import it later and `polyx` is
32
+ * appended last, which puts us back to outranking utilities — no worse than the
33
+ * unlayered behaviour, but not the fix either. Import us first.
13
34
  */
14
35
 
36
+ @layer theme, base, polyx, components, utilities;
37
+
38
+ @layer polyx {
39
+
15
40
  .polyx-signin {
16
41
  /* Colors */
17
42
  --polyx-brand: #4f46e5;
@@ -683,3 +708,8 @@
683
708
  height: 1rem;
684
709
  color: var(--polyx-muted-fg);
685
710
  }
711
+
712
+ /* Closes `@layer polyx` — opened at the top of the file. The rules above are left
713
+ unindented deliberately: wrapping the sheet in a layer is a cascade change, not a
714
+ reformat, and re-indenting 600 lines would bury it in the diff. */
715
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@poly-x/next",
3
- "version": "0.1.0-alpha.12",
3
+ "version": "0.1.0-alpha.14",
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.12",
51
- "@poly-x/react": "0.1.0-alpha.12"
50
+ "@poly-x/core": "0.1.0-alpha.14",
51
+ "@poly-x/react": "0.1.0-alpha.14"
52
52
  },
53
53
  "peerDependencies": {
54
54
  "next": ">=14",