@palbase/web 7.2.1 → 7.3.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.
Files changed (38) hide show
  1. package/dist/{analytics-facade-DLfnVVwL.d.ts → analytics-facade-BER0EyYT.d.ts} +8 -3
  2. package/dist/{analytics-facade-jyXv7Cu4.d.cts → analytics-facade-CQXCQSoF.d.cts} +8 -3
  3. package/dist/{chunk-GITHW5JK.js → chunk-YBURINUN.js} +60 -14
  4. package/dist/chunk-YBURINUN.js.map +1 -0
  5. package/dist/gen/cli.cjs +15 -24
  6. package/dist/gen/cli.cjs.map +1 -1
  7. package/dist/gen/cli.js +15 -24
  8. package/dist/gen/cli.js.map +1 -1
  9. package/dist/index.cjs +51 -4
  10. package/dist/index.cjs.map +1 -1
  11. package/dist/index.d.cts +3 -3
  12. package/dist/index.d.ts +3 -3
  13. package/dist/index.js +1 -1
  14. package/dist/internal.cjs +59 -13
  15. package/dist/internal.cjs.map +1 -1
  16. package/dist/internal.d.cts +3 -3
  17. package/dist/internal.d.ts +3 -3
  18. package/dist/internal.js +1 -1
  19. package/dist/next/client.cjs +67 -21
  20. package/dist/next/client.cjs.map +1 -1
  21. package/dist/next/client.js +5 -3
  22. package/dist/next/client.js.map +1 -1
  23. package/dist/next/index.cjs +73 -27
  24. package/dist/next/index.cjs.map +1 -1
  25. package/dist/next/index.d.cts +2 -22
  26. package/dist/next/index.d.ts +2 -22
  27. package/dist/next/index.js +5 -4
  28. package/dist/next/index.js.map +1 -1
  29. package/dist/{pb-DdzpEkPY.d.ts → pb-BRlmBIAm.d.ts} +1 -1
  30. package/dist/{pb-CfYQGEn0.d.cts → pb-WAQvwCfM.d.cts} +1 -1
  31. package/dist/react/index.cjs +50 -3
  32. package/dist/react/index.cjs.map +1 -1
  33. package/dist/react/index.d.cts +1 -1
  34. package/dist/react/index.d.ts +1 -1
  35. package/dist/react/index.js +1 -1
  36. package/package.json +13 -13
  37. package/LICENSE +0 -21
  38. package/dist/chunk-GITHW5JK.js.map +0 -1
@@ -1,29 +1,9 @@
1
1
  import { NextRequest } from 'next/server';
2
- import { P as PB } from '../pb-CfYQGEn0.cjs';
3
- import '../analytics-facade-jyXv7Cu4.cjs';
2
+ import { P as PB } from '../pb-WAQvwCfM.cjs';
3
+ import '../analytics-facade-CQXCQSoF.cjs';
4
4
  import '../storage-BPaeSG8K.cjs';
5
5
  import './pooled-flags-4GtDtsiu.js';
6
6
 
7
- /**
8
- * handleAuthCallback — the OAuth completion route handler factory.
9
- *
10
- * The app's redirect_uri (registered when calling signInWithOAuth with
11
- * `redirectTo: '<app>/auth/callback?provider=google&next=/dash'`) lands
12
- * here with `?code&state` from the provider. The handler exchanges them
13
- * SERVER-SIDE against palauth (PKCE verifier + state live server-side; the
14
- * browser never holds them), writes the session cookies onto a redirect
15
- * response, and sends the user on.
16
- *
17
- * ```ts
18
- * // app/auth/callback/route.ts
19
- * import { handleAuthCallback } from '@palbase/web/next';
20
- * export const GET = handleAuthCallback({ defaultNext: '/' });
21
- * ```
22
- *
23
- * MODULE-GRAPH RULE (same as ./index.ts): 'next/server' only lazily; the
24
- * type-only NextRequest import erases at build time.
25
- */
26
-
27
7
  interface HandleAuthCallbackOptions {
28
8
  /** Where to land when the callback carries no (valid) `next` param — and on every error/MFA redirect. Default '/'. */
29
9
  defaultNext?: string;
@@ -1,29 +1,9 @@
1
1
  import { NextRequest } from 'next/server';
2
- import { P as PB } from '../pb-DdzpEkPY.js';
3
- import '../analytics-facade-DLfnVVwL.js';
2
+ import { P as PB } from '../pb-BRlmBIAm.js';
3
+ import '../analytics-facade-BER0EyYT.js';
4
4
  import '../storage-BPaeSG8K.js';
5
5
  import './pooled-flags-4GtDtsiu.js';
6
6
 
7
- /**
8
- * handleAuthCallback — the OAuth completion route handler factory.
9
- *
10
- * The app's redirect_uri (registered when calling signInWithOAuth with
11
- * `redirectTo: '<app>/auth/callback?provider=google&next=/dash'`) lands
12
- * here with `?code&state` from the provider. The handler exchanges them
13
- * SERVER-SIDE against palauth (PKCE verifier + state live server-side; the
14
- * browser never holds them), writes the session cookies onto a redirect
15
- * response, and sends the user on.
16
- *
17
- * ```ts
18
- * // app/auth/callback/route.ts
19
- * import { handleAuthCallback } from '@palbase/web/next';
20
- * export const GET = handleAuthCallback({ defaultNext: '/' });
21
- * ```
22
- *
23
- * MODULE-GRAPH RULE (same as ./index.ts): 'next/server' only lazily; the
24
- * type-only NextRequest import erases at build time.
25
- */
26
-
27
7
  interface HandleAuthCallbackOptions {
28
8
  /** Where to land when the callback carries no (valid) `next` param — and on every error/MFA redirect. Default '/'. */
29
9
  defaultNext?: string;
@@ -14,12 +14,13 @@ import {
14
14
  buildRuntime,
15
15
  createBoundClient,
16
16
  getRuntime
17
- } from "../chunk-GITHW5JK.js";
17
+ } from "../chunk-YBURINUN.js";
18
18
  import {
19
19
  BackendError
20
20
  } from "../chunk-ACCJV6FV.js";
21
21
  import {
22
- environmentRefFromApiKey
22
+ environmentRefFromApiKey,
23
+ environmentRefFromPublishableApiKey
23
24
  } from "../chunk-CFDU23TB.js";
24
25
  import "../chunk-PZ5AY32C.js";
25
26
 
@@ -110,7 +111,7 @@ function handleAuthCallback(opts) {
110
111
  const wire = asWireAuthResult(raw);
111
112
  if (!wire) return redirect(fallback, { auth_error: "oauth_exchange_failed" });
112
113
  const response = redirect(safeNextPath(params.get("next"), request.nextUrl.origin) ?? fallback);
113
- const write = encodeSessionCookiesDecoded(config.environmentRef, {
114
+ const write = encodeSessionCookiesDecoded(environmentRefFromPublishableApiKey(config.apiKey), {
114
115
  accessToken: wire.access_token,
115
116
  refreshToken: wire.refresh_token,
116
117
  expiresAt: Date.now() + wire.expires_in * 1e3
@@ -201,7 +202,7 @@ async function pbServer(opts) {
201
202
  "import the generated palbe.gen.ts once in app/layout.tsx \u2014 the root-layout import configures Server Components and Route Handlers too."
202
203
  );
203
204
  const store = opts?.cookies ?? await nextCookieStore();
204
- const ref = config.environmentRef;
205
+ const ref = environmentRefFromPublishableApiKey(config.apiKey);
205
206
  const rt = buildRuntime({ ...config, storage: serverCookieAdapter(store, ref) });
206
207
  return createBoundClient(rt);
207
208
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/next/global-config.ts","../../src/next/callback.ts","../../src/next/server.ts"],"sourcesContent":["/**\n * Global-config lookup for the palbe/next surfaces that run inside the\n * SERVER module graph (pbServer, handleAuthCallback) — the graph the root\n * layout's `import './palbe.gen'` configures.\n *\n * This module reaches `../internal.js` (and therefore the whole runtime\n * graph: livekit-client + the MLS WASM loader) on purpose — those surfaces\n * build a real runtime anyway. It is kept OUT of ./shared.ts so the\n * middleware entry, which must stay Edge-lean, never pulls it in.\n */\nimport type { PalbeConfig } from '../config.js';\nimport { BackendError } from '../errors.js';\nimport { getRuntime } from '../internal.js';\n\n/**\n * Global config with a surface-specific guided error when absent. Each\n * surface names the import location that configures ITS module graph.\n */\nexport function requireGlobalConfig(hint: string): PalbeConfig {\n try {\n return getRuntime().config;\n } catch {\n throw new BackendError('notConfigured', {\n code: 'not_configured',\n message: `Palbe is not configured in this module graph. Run 'palbase web link' and ${hint}`,\n });\n }\n}\n","/**\n * handleAuthCallback — the OAuth completion route handler factory.\n *\n * The app's redirect_uri (registered when calling signInWithOAuth with\n * `redirectTo: '<app>/auth/callback?provider=google&next=/dash'`) lands\n * here with `?code&state` from the provider. The handler exchanges them\n * SERVER-SIDE against palauth (PKCE verifier + state live server-side; the\n * browser never holds them), writes the session cookies onto a redirect\n * response, and sends the user on.\n *\n * ```ts\n * // app/auth/callback/route.ts\n * import { handleAuthCallback } from '@palbase/web/next';\n * export const GET = handleAuthCallback({ defaultNext: '/' });\n * ```\n *\n * MODULE-GRAPH RULE (same as ./index.ts): 'next/server' only lazily; the\n * type-only NextRequest import erases at build time.\n */\nimport type { NextRequest } from 'next/server';\nimport { asWireAuthResult } from '../auth-wire.js';\nimport { encodeSessionCookiesDecoded, SESSION_COOKIE_ATTRS } from './cookie-codec.js';\nimport { requireGlobalConfig } from './global-config.js';\nimport { importNextServer } from './shared.js';\n\nexport interface HandleAuthCallbackOptions {\n /** Where to land when the callback carries no (valid) `next` param — and on every error/MFA redirect. Default '/'. */\n defaultNext?: string;\n}\n\n/**\n * Open-redirect guard: `next` rides the OAuth redirect querystring, so it is\n * attacker-influencable — only same-origin, path-absolute relative paths are\n * honored, and the post-auth victim is never sent off-origin.\n *\n * A leading-'/' heuristic is NOT enough. The value is later resolved with\n * `new URL(next, base)`, and the WHATWG URL parser STRIPS ASCII tab (0x09),\n * LF (0x0A) and CR (0x0D) before parsing — so `/<TAB>/evil.com` slips past a\n * \"next[1] is not '/'\" check yet parses to `//evil.com` → `https://evil.com`\n * (CWE-601 open redirect). We defend with TWO independent gates that must\n * BOTH pass, matching OWASP unvalidated-redirect guidance:\n *\n * 1. RAW-STRING gate (reject before normalization can hide the payload):\n * - reject any C0 control char (\\x00-\\x1F, incl. \\t \\n \\r) or other\n * whitespace — these are exactly what URL parsing strips/normalizes\n * away;\n * - require a path-absolute prefix: a single '/' NOT followed by '/' or\n * '\\\\' (blocks '//host' protocol-relative and '/\\\\host' backslash\n * tricks);\n * - reject a scheme: any ':' appearing before the first '/' ('javascript:',\n * 'https:'). Path-absolute inputs start with '/', so this also rejects\n * bare 'foo:bar'.\n * 2. CANONICAL gate: resolve `new URL(next, origin)` and require the result\n * to land on the SAME origin. This is the authoritative check — it sees\n * exactly what the browser will, after normalization.\n *\n * Anything ambiguous returns null so the caller falls back to a safe default.\n */\nfunction safeNextPath(next: string | null, origin: string): string | null {\n if (!next) return null;\n // 1a. No control chars / whitespace — URL parsing would strip these and\n // change what the path means (the \\t \\n \\r open-redirect class).\n // biome-ignore lint/suspicious/noControlCharactersInRegex: rejecting C0 is the point of the guard.\n if (/[\\u0000-\\u0020\\u007f\\s]/.test(next)) return null;\n // 1b. Must be path-absolute: a single leading '/', not '//' or '/\\\\'.\n if (next[0] !== '/' || next[1] === '/' || next[1] === '\\\\') return null;\n // 1c. No scheme before the first path separator (defense in depth; a\n // path-absolute string can't carry one, but reject explicitly).\n const firstSlash = next.indexOf('/');\n const firstColon = next.indexOf(':');\n if (firstColon !== -1 && firstColon < firstSlash) return null;\n // 2. Canonical same-origin check — resolve as the browser/redirect helper\n // will and confirm we never leave this origin.\n try {\n if (new URL(next, origin).origin !== origin) return null;\n } catch {\n return null;\n }\n return next;\n}\n\nfunction wireErrorCode(raw: unknown): string {\n if (typeof raw === 'object' && raw !== null) {\n const code = (raw as Record<string, unknown>).error;\n if (typeof code === 'string' && code !== '') return code;\n }\n return 'oauth_exchange_failed';\n}\n\n/**\n * Build the callback route handler. Outcomes (all 307 redirects, all with\n * `Cache-Control: private, no-store` — auth outcomes must never cache):\n * - provider `error` param → `?auth_error=<error>` (+ `auth_error_description`)\n * - missing code/state/provider → `?auth_error=invalid_callback`\n * - exchanged AuthResult → session cookies on the redirect to `next ?? defaultNext ?? '/'`\n * - MFA union → `?mfa_token=…&mfa_factors=a,b` on defaultNext, NO cookies\n * - exchange failure → `?auth_error=<wire code | oauth_exchange_failed>`\n */\nexport function handleAuthCallback(\n opts?: HandleAuthCallbackOptions,\n): (request: NextRequest) => Promise<Response> {\n return async (request) => {\n const config = requireGlobalConfig(\n 'import the generated palbe.gen.ts once in app/layout.tsx — route handlers share the server module graph with the root layout.',\n );\n const { NextResponse } = await importNextServer('handleAuthCallback');\n const fallback = opts?.defaultNext ?? '/';\n\n const redirect = (path: string, query?: Record<string, string>) => {\n const url = new URL(path, request.nextUrl);\n for (const [key, value] of Object.entries(query ?? {})) url.searchParams.set(key, value);\n const response = NextResponse.redirect(url); // 307 — the callback GET stays a GET\n response.headers.set('cache-control', 'private, no-store');\n return response;\n };\n\n const params = request.nextUrl.searchParams;\n const providerError = params.get('error');\n if (providerError) {\n const query: Record<string, string> = { auth_error: providerError };\n const description = params.get('error_description');\n if (description) query.auth_error_description = description;\n return redirect(fallback, query);\n }\n\n const code = params.get('code');\n const state = params.get('state');\n const provider = params.get('provider');\n if (!code || !state || !provider) {\n return redirect(fallback, { auth_error: 'invalid_callback' });\n }\n\n let exchange: Response;\n try {\n exchange = await fetch(\n `${config.url}/auth/oauth/${encodeURIComponent(provider)}/callback` +\n `?code=${encodeURIComponent(code)}&state=${encodeURIComponent(state)}`,\n { headers: { apikey: config.apiKey } },\n );\n } catch {\n return redirect(fallback, { auth_error: 'oauth_exchange_failed' });\n }\n let raw: unknown = null;\n try {\n raw = await exchange.json();\n } catch {\n // malformed body — the shape checks below turn this into auth_error\n }\n if (!exchange.ok) return redirect(fallback, { auth_error: wireErrorCode(raw) });\n\n if (typeof raw === 'object' && raw !== null) {\n const obj = raw as Record<string, unknown>;\n // MFA union: relay the challenge to the app — NO cookies until it completes.\n if (obj.mfa_required === true && typeof obj.mfa_token === 'string') {\n const factors = Array.isArray(obj.mfa_factors)\n ? obj.mfa_factors.filter((f): f is string => typeof f === 'string')\n : [];\n return redirect(fallback, { mfa_token: obj.mfa_token, mfa_factors: factors.join(',') });\n }\n }\n\n // Zombie-session guard (shared with the auth facade): cookies are only\n // ever written for a FULLY-proven AuthResult.\n const wire = asWireAuthResult(raw);\n if (!wire) return redirect(fallback, { auth_error: 'oauth_exchange_failed' });\n\n const response = redirect(safeNextPath(params.get('next'), request.nextUrl.origin) ?? fallback);\n // DECODED codec values: NextResponse.cookies percent-encodes on write,\n // producing exactly the shared-codec wire bytes (encodeSessionCookiesDecoded).\n const write = encodeSessionCookiesDecoded(config.environmentRef, {\n accessToken: wire.access_token,\n refreshToken: wire.refresh_token,\n expiresAt: Date.now() + wire.expires_in * 1000,\n });\n for (const { name, value } of write.set) {\n response.cookies.set(name, value, SESSION_COOKIE_ATTRS);\n }\n // Codec overflow guard: expire one-past-the-end of the written run.\n for (const name of write.clear) {\n response.cookies.set(name, '', { ...SESSION_COOKIE_ATTRS, maxAge: 0 });\n }\n return response;\n };\n}\n","/**\n * pbServer — the per-request server client of the Next.js adapter.\n *\n * Every call builds a FRESH runtime over the request's cookie store and\n * returns a runtime-BOUND `PB` client: typed namespaces, `call`, `upload`\n * and `auth` all resolve that runtime, never the process-global slot — two\n * bound clients serving concurrent requests cannot leak each other's\n * sessions (see createBoundClient). The store itself is read lazily per\n * adapter operation, so nothing is snapshotted beyond the call.\n *\n * MODULE-GRAPH RULE (same as ./index.ts): 'next/headers' is imported ONLY\n * lazily inside the default-store path — importing this module, and using\n * pbServer with an injected store, must work without next installed.\n */\nimport { BackendError } from '../errors.js';\nimport { buildRuntime, createBoundClient } from '../internal.js';\nimport type { PB } from '../pb.js';\nimport type { PersistedSession, SessionStorageAdapter } from '../storage.js';\nimport {\n clearedSessionCookieNames,\n decodeSessionCookies,\n encodeSessionCookiesDecoded,\n SESSION_COOKIE_ATTRS,\n} from './cookie-codec.js';\nimport { requireGlobalConfig } from './global-config.js';\n\n/**\n * Structural slice of Next's request cookie store (`await cookies()`); any\n * object honoring it works — the seam for tests and non-Next servers.\n * `set`/`delete` are optional because RSC-scope stores are read-only.\n */\nexport interface CookieStoreLike {\n get(name: string): { name: string; value: string } | undefined;\n getAll?(): Array<{ name: string; value: string }>;\n set?(name: string, value: string, options?: Record<string, unknown>): void;\n delete?(name: string): void;\n}\n\nfunction nextRequired(cause: unknown): BackendError {\n const detail = cause instanceof Error && cause.message ? ` (${cause.message})` : '';\n return new BackendError('validation', {\n code: 'next_required',\n message:\n 'pbServer() could not resolve a Next.js request cookie store: call it inside a Server Component, Server Action or Route Handler — or pass a store explicitly via pbServer({ cookies })' +\n `${detail}.`,\n });\n}\n\n/**\n * Next CONTROL-FLOW errors must pass through UNWRAPPED. During static\n * prerender (`next build`) `cookies()` throws DynamicServerError (digest\n * 'DYNAMIC_SERVER_USAGE'), which Next catches upstream to mark the route\n * dynamic — wrapping it into a BackendError would turn that bailout signal\n * into a real prerender error and FAIL THE BUILD of every page calling\n * pbServer (caught live in the P3 Next smoke). 'NEXT_'-prefixed digests are\n * the same sentinel family (redirect / not-found / static-gen bailout).\n * Genuine outside-request-scope errors carry no digest and still map to\n * next_required.\n */\nexport function isNextControlFlowError(err: unknown): boolean {\n if (typeof err !== 'object' || err === null) return false;\n const digest = (err as { digest?: unknown }).digest;\n return (\n typeof digest === 'string' && (digest === 'DYNAMIC_SERVER_USAGE' || digest.startsWith('NEXT_'))\n );\n}\n\n/** Lazy default store: `await (await import('next/headers')).cookies()`. */\nasync function nextCookieStore(): Promise<CookieStoreLike> {\n let cookies: () => Promise<CookieStoreLike>;\n try {\n ({ cookies } = await import('next/headers'));\n } catch (cause) {\n throw nextRequired(cause); // next not installed / not resolvable\n }\n try {\n return await cookies();\n } catch (cause) {\n if (isNextControlFlowError(cause)) throw cause; // static-prerender bailout etc.\n throw nextRequired(cause); // installed, but outside a request scope\n }\n}\n\n/** Delete one cookie: set-with-maxAge-0 when the store can set (parity with the browser side), else delete, else swallow (read-only RSC store). */\nfunction removeCookie(store: CookieStoreLike, name: string): void {\n try {\n if (store.set) store.set(name, '', { ...SESSION_COOKIE_ATTRS, maxAge: 0 });\n else store.delete?.(name);\n } catch {\n // RSC read-only store — cookie mutation is the middleware's job there\n }\n}\n\n/**\n * SessionStorageAdapter over the request cookie store.\n *\n * SAVE SEMANTICS (decision, documented): only FULL sessions (access token +\n * expiry present) are written; a refresh-only save is SKIPPED with no throw\n * rather than written as {a:'',e:0}. The cookie is SHARED with the browser —\n * degrading it would drop an access token the browser may still be using and\n * force a refresh per page-load. The refresh-only branch is a defensive\n * legacy path anyway: every successful refresh saves a full session.\n *\n * All writes swallow per-cookie failures: in RSC scope Next's store throws\n * on mutation; the request still proceeds with the in-memory session but the\n * rotated cookie is NOT persisted. palbeMiddleware is therefore REQUIRED —\n * not optional — for session-bearing RSC apps: without it every RSC render\n * re-refreshes from the SAME cookie refresh token, and once two such\n * refreshes land more than 30s apart (outside palauth's rotation grace) the\n * reuse detector REVOKES THE WHOLE TOKEN FAMILY — the user is\n * force-logged-out everywhere.\n *\n * Values are written DECODED (encodeSessionCookiesDecoded): Next's real\n * cookies() store percent-encodes on serialization, yielding exactly the\n * shared-codec wire bytes; a raw store (tests, custom servers) persists the\n * bare JSON, which the load path's decode tolerates identically (tokens are\n * %-free base64url, so decodeURIComponent is the identity on them).\n */\nfunction serverCookieAdapter(\n store: CookieStoreLike,\n environmentRef: string,\n): SessionStorageAdapter {\n const present = (name: string): boolean => store.get(name) !== undefined;\n return {\n load(): PersistedSession | null {\n const stored = decodeSessionCookies((name) => store.get(name)?.value, environmentRef);\n if (!stored) return null;\n // A browser-side refresh-only write round-trips as a:'' / e:0 —\n // normalize to the legacy shape so hydration takes the expired-trick.\n return stored.accessToken && stored.expiresAt > 0\n ? {\n refreshToken: stored.refreshToken,\n accessToken: stored.accessToken,\n expiresAt: stored.expiresAt,\n }\n : { refreshToken: stored.refreshToken };\n },\n save(session: PersistedSession): void {\n if (!session.accessToken || !session.expiresAt) return; // see SAVE SEMANTICS above\n for (const name of clearedSessionCookieNames(environmentRef, present)) {\n removeCookie(store, name);\n }\n const { set, clear } = encodeSessionCookiesDecoded(environmentRef, {\n accessToken: session.accessToken,\n refreshToken: session.refreshToken,\n expiresAt: session.expiresAt,\n });\n for (const { name, value } of set) {\n try {\n store.set?.(name, value, { ...SESSION_COOKIE_ATTRS });\n } catch {\n // RSC read-only store\n }\n }\n // Overflow guard (codec contract): delete one-past-the-end so a stale\n // orphan chunk behind a gap can never join a future chunk run.\n for (const name of clear) removeCookie(store, name);\n },\n clear(): void {\n for (const name of clearedSessionCookieNames(environmentRef, present)) {\n removeCookie(store, name);\n }\n },\n };\n}\n\n/**\n * Build a per-request `PB` client over the request's cookies. Reads the\n * session the browser SDK wrote (shared cookie codec), refreshes pre-flight\n * when expired, and — where the store is writable (Server Actions, Route\n * Handlers) — persists the rotated session back.\n *\n * Each call builds a fresh runtime (a handful of small objects, no I/O) —\n * cheap enough to call per data access; code paths invoking it many times\n * within one request MAY memoize per-request (e.g. React's `cache()`), but\n * don't have to.\n *\n * ```ts\n * const pb = await pbServer();\n * const todos = await pb.todos.list();\n * ```\n */\nexport async function pbServer(opts?: { cookies?: CookieStoreLike }): Promise<PB> {\n const config = requireGlobalConfig(\n 'import the generated palbe.gen.ts once in app/layout.tsx — the root-layout import configures Server Components and Route Handlers too.',\n );\n const store = opts?.cookies ?? (await nextCookieStore());\n const ref = config.environmentRef;\n const rt = buildRuntime({ ...config, storage: serverCookieAdapter(store, ref) });\n return createBoundClient(rt);\n}\n"],"mappings":"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","names":["response"]}
1
+ {"version":3,"sources":["../../src/next/global-config.ts","../../src/next/callback.ts","../../src/next/server.ts"],"sourcesContent":["/**\n * Global-config lookup for the palbe/next surfaces that run inside the\n * SERVER module graph (pbServer, handleAuthCallback) — the graph the root\n * layout's `import './palbe.gen'` configures.\n *\n * This module reaches `../internal.js` (and therefore the whole runtime\n * graph: livekit-client + the MLS WASM loader) on purpose — those surfaces\n * build a real runtime anyway. It is kept OUT of ./shared.ts so the\n * middleware entry, which must stay Edge-lean, never pulls it in.\n */\nimport type { PalbeConfig } from '../config.js';\nimport { BackendError } from '../errors.js';\nimport { getRuntime } from '../internal.js';\n\n/**\n * Global config with a surface-specific guided error when absent. Each\n * surface names the import location that configures ITS module graph.\n */\nexport function requireGlobalConfig(hint: string): PalbeConfig {\n try {\n return getRuntime().config;\n } catch {\n throw new BackendError('notConfigured', {\n code: 'not_configured',\n message: `Palbe is not configured in this module graph. Run 'palbase web link' and ${hint}`,\n });\n }\n}\n","/**\n * handleAuthCallback — the OAuth completion route handler factory.\n *\n * The app's redirect_uri (registered when calling signInWithOAuth with\n * `redirectTo: '<app>/auth/callback?provider=google&next=/dash'`) lands\n * here with `?code&state` from the provider. The handler exchanges them\n * SERVER-SIDE against palauth (PKCE verifier + state live server-side; the\n * browser never holds them), writes the session cookies onto a redirect\n * response, and sends the user on.\n *\n * ```ts\n * // app/auth/callback/route.ts\n * import { handleAuthCallback } from '@palbase/web/next';\n * export const GET = handleAuthCallback({ defaultNext: '/' });\n * ```\n *\n * MODULE-GRAPH RULE (same as ./index.ts): 'next/server' only lazily; the\n * type-only NextRequest import erases at build time.\n */\nimport { environmentRefFromPublishableApiKey } from '../api-key.js';\nimport type { NextRequest } from 'next/server';\nimport { asWireAuthResult } from '../auth-wire.js';\nimport { encodeSessionCookiesDecoded, SESSION_COOKIE_ATTRS } from './cookie-codec.js';\nimport { requireGlobalConfig } from './global-config.js';\nimport { importNextServer } from './shared.js';\n\nexport interface HandleAuthCallbackOptions {\n /** Where to land when the callback carries no (valid) `next` param — and on every error/MFA redirect. Default '/'. */\n defaultNext?: string;\n}\n\n/**\n * Open-redirect guard: `next` rides the OAuth redirect querystring, so it is\n * attacker-influencable — only same-origin, path-absolute relative paths are\n * honored, and the post-auth victim is never sent off-origin.\n *\n * A leading-'/' heuristic is NOT enough. The value is later resolved with\n * `new URL(next, base)`, and the WHATWG URL parser STRIPS ASCII tab (0x09),\n * LF (0x0A) and CR (0x0D) before parsing — so `/<TAB>/evil.com` slips past a\n * \"next[1] is not '/'\" check yet parses to `//evil.com` → `https://evil.com`\n * (CWE-601 open redirect). We defend with TWO independent gates that must\n * BOTH pass, matching OWASP unvalidated-redirect guidance:\n *\n * 1. RAW-STRING gate (reject before normalization can hide the payload):\n * - reject any C0 control char (\\x00-\\x1F, incl. \\t \\n \\r) or other\n * whitespace — these are exactly what URL parsing strips/normalizes\n * away;\n * - require a path-absolute prefix: a single '/' NOT followed by '/' or\n * '\\\\' (blocks '//host' protocol-relative and '/\\\\host' backslash\n * tricks);\n * - reject a scheme: any ':' appearing before the first '/' ('javascript:',\n * 'https:'). Path-absolute inputs start with '/', so this also rejects\n * bare 'foo:bar'.\n * 2. CANONICAL gate: resolve `new URL(next, origin)` and require the result\n * to land on the SAME origin. This is the authoritative check — it sees\n * exactly what the browser will, after normalization.\n *\n * Anything ambiguous returns null so the caller falls back to a safe default.\n */\nfunction safeNextPath(next: string | null, origin: string): string | null {\n if (!next) return null;\n // 1a. No control chars / whitespace — URL parsing would strip these and\n // change what the path means (the \\t \\n \\r open-redirect class).\n // biome-ignore lint/suspicious/noControlCharactersInRegex: rejecting C0 is the point of the guard.\n if (/[\\u0000-\\u0020\\u007f\\s]/.test(next)) return null;\n // 1b. Must be path-absolute: a single leading '/', not '//' or '/\\\\'.\n if (next[0] !== '/' || next[1] === '/' || next[1] === '\\\\') return null;\n // 1c. No scheme before the first path separator (defense in depth; a\n // path-absolute string can't carry one, but reject explicitly).\n const firstSlash = next.indexOf('/');\n const firstColon = next.indexOf(':');\n if (firstColon !== -1 && firstColon < firstSlash) return null;\n // 2. Canonical same-origin check — resolve as the browser/redirect helper\n // will and confirm we never leave this origin.\n try {\n if (new URL(next, origin).origin !== origin) return null;\n } catch {\n return null;\n }\n return next;\n}\n\nfunction wireErrorCode(raw: unknown): string {\n if (typeof raw === 'object' && raw !== null) {\n const code = (raw as Record<string, unknown>).error;\n if (typeof code === 'string' && code !== '') return code;\n }\n return 'oauth_exchange_failed';\n}\n\n/**\n * Build the callback route handler. Outcomes (all 307 redirects, all with\n * `Cache-Control: private, no-store` — auth outcomes must never cache):\n * - provider `error` param → `?auth_error=<error>` (+ `auth_error_description`)\n * - missing code/state/provider → `?auth_error=invalid_callback`\n * - exchanged AuthResult → session cookies on the redirect to `next ?? defaultNext ?? '/'`\n * - MFA union → `?mfa_token=…&mfa_factors=a,b` on defaultNext, NO cookies\n * - exchange failure → `?auth_error=<wire code | oauth_exchange_failed>`\n */\nexport function handleAuthCallback(\n opts?: HandleAuthCallbackOptions,\n): (request: NextRequest) => Promise<Response> {\n return async (request) => {\n const config = requireGlobalConfig(\n 'import the generated palbe.gen.ts once in app/layout.tsx — route handlers share the server module graph with the root layout.',\n );\n const { NextResponse } = await importNextServer('handleAuthCallback');\n const fallback = opts?.defaultNext ?? '/';\n\n const redirect = (path: string, query?: Record<string, string>) => {\n const url = new URL(path, request.nextUrl);\n for (const [key, value] of Object.entries(query ?? {})) url.searchParams.set(key, value);\n const response = NextResponse.redirect(url); // 307 — the callback GET stays a GET\n response.headers.set('cache-control', 'private, no-store');\n return response;\n };\n\n const params = request.nextUrl.searchParams;\n const providerError = params.get('error');\n if (providerError) {\n const query: Record<string, string> = { auth_error: providerError };\n const description = params.get('error_description');\n if (description) query.auth_error_description = description;\n return redirect(fallback, query);\n }\n\n const code = params.get('code');\n const state = params.get('state');\n const provider = params.get('provider');\n if (!code || !state || !provider) {\n return redirect(fallback, { auth_error: 'invalid_callback' });\n }\n\n let exchange: Response;\n try {\n exchange = await fetch(\n `${config.url}/auth/oauth/${encodeURIComponent(provider)}/callback` +\n `?code=${encodeURIComponent(code)}&state=${encodeURIComponent(state)}`,\n { headers: { apikey: config.apiKey } },\n );\n } catch {\n return redirect(fallback, { auth_error: 'oauth_exchange_failed' });\n }\n let raw: unknown = null;\n try {\n raw = await exchange.json();\n } catch {\n // malformed body — the shape checks below turn this into auth_error\n }\n if (!exchange.ok) return redirect(fallback, { auth_error: wireErrorCode(raw) });\n\n if (typeof raw === 'object' && raw !== null) {\n const obj = raw as Record<string, unknown>;\n // MFA union: relay the challenge to the app — NO cookies until it completes.\n if (obj.mfa_required === true && typeof obj.mfa_token === 'string') {\n const factors = Array.isArray(obj.mfa_factors)\n ? obj.mfa_factors.filter((f): f is string => typeof f === 'string')\n : [];\n return redirect(fallback, { mfa_token: obj.mfa_token, mfa_factors: factors.join(',') });\n }\n }\n\n // Zombie-session guard (shared with the auth facade): cookies are only\n // ever written for a FULLY-proven AuthResult.\n const wire = asWireAuthResult(raw);\n if (!wire) return redirect(fallback, { auth_error: 'oauth_exchange_failed' });\n\n const response = redirect(safeNextPath(params.get('next'), request.nextUrl.origin) ?? fallback);\n // DECODED codec values: NextResponse.cookies percent-encodes on write,\n // producing exactly the shared-codec wire bytes (encodeSessionCookiesDecoded).\n const write = encodeSessionCookiesDecoded(environmentRefFromPublishableApiKey(config.apiKey), {\n accessToken: wire.access_token,\n refreshToken: wire.refresh_token,\n expiresAt: Date.now() + wire.expires_in * 1000,\n });\n for (const { name, value } of write.set) {\n response.cookies.set(name, value, SESSION_COOKIE_ATTRS);\n }\n // Codec overflow guard: expire one-past-the-end of the written run.\n for (const name of write.clear) {\n response.cookies.set(name, '', { ...SESSION_COOKIE_ATTRS, maxAge: 0 });\n }\n return response;\n };\n}\n","/**\n * pbServer — the per-request server client of the Next.js adapter.\n *\n * Every call builds a FRESH runtime over the request's cookie store and\n * returns a runtime-BOUND `PB` client: typed namespaces, `call`, `upload`\n * and `auth` all resolve that runtime, never the process-global slot — two\n * bound clients serving concurrent requests cannot leak each other's\n * sessions (see createBoundClient). The store itself is read lazily per\n * adapter operation, so nothing is snapshotted beyond the call.\n *\n * MODULE-GRAPH RULE (same as ./index.ts): 'next/headers' is imported ONLY\n * lazily inside the default-store path — importing this module, and using\n * pbServer with an injected store, must work without next installed.\n */\nimport { environmentRefFromPublishableApiKey } from '../api-key.js';\nimport { BackendError } from '../errors.js';\nimport { buildRuntime, createBoundClient } from '../internal.js';\nimport type { PB } from '../pb.js';\nimport type { PersistedSession, SessionStorageAdapter } from '../storage.js';\nimport {\n clearedSessionCookieNames,\n decodeSessionCookies,\n encodeSessionCookiesDecoded,\n SESSION_COOKIE_ATTRS,\n} from './cookie-codec.js';\nimport { requireGlobalConfig } from './global-config.js';\n\n/**\n * Structural slice of Next's request cookie store (`await cookies()`); any\n * object honoring it works — the seam for tests and non-Next servers.\n * `set`/`delete` are optional because RSC-scope stores are read-only.\n */\nexport interface CookieStoreLike {\n get(name: string): { name: string; value: string } | undefined;\n getAll?(): Array<{ name: string; value: string }>;\n set?(name: string, value: string, options?: Record<string, unknown>): void;\n delete?(name: string): void;\n}\n\nfunction nextRequired(cause: unknown): BackendError {\n const detail = cause instanceof Error && cause.message ? ` (${cause.message})` : '';\n return new BackendError('validation', {\n code: 'next_required',\n message:\n 'pbServer() could not resolve a Next.js request cookie store: call it inside a Server Component, Server Action or Route Handler — or pass a store explicitly via pbServer({ cookies })' +\n `${detail}.`,\n });\n}\n\n/**\n * Next CONTROL-FLOW errors must pass through UNWRAPPED. During static\n * prerender (`next build`) `cookies()` throws DynamicServerError (digest\n * 'DYNAMIC_SERVER_USAGE'), which Next catches upstream to mark the route\n * dynamic — wrapping it into a BackendError would turn that bailout signal\n * into a real prerender error and FAIL THE BUILD of every page calling\n * pbServer (caught live in the P3 Next smoke). 'NEXT_'-prefixed digests are\n * the same sentinel family (redirect / not-found / static-gen bailout).\n * Genuine outside-request-scope errors carry no digest and still map to\n * next_required.\n */\nexport function isNextControlFlowError(err: unknown): boolean {\n if (typeof err !== 'object' || err === null) return false;\n const digest = (err as { digest?: unknown }).digest;\n return (\n typeof digest === 'string' && (digest === 'DYNAMIC_SERVER_USAGE' || digest.startsWith('NEXT_'))\n );\n}\n\n/** Lazy default store: `await (await import('next/headers')).cookies()`. */\nasync function nextCookieStore(): Promise<CookieStoreLike> {\n let cookies: () => Promise<CookieStoreLike>;\n try {\n ({ cookies } = await import('next/headers'));\n } catch (cause) {\n throw nextRequired(cause); // next not installed / not resolvable\n }\n try {\n return await cookies();\n } catch (cause) {\n if (isNextControlFlowError(cause)) throw cause; // static-prerender bailout etc.\n throw nextRequired(cause); // installed, but outside a request scope\n }\n}\n\n/** Delete one cookie: set-with-maxAge-0 when the store can set (parity with the browser side), else delete, else swallow (read-only RSC store). */\nfunction removeCookie(store: CookieStoreLike, name: string): void {\n try {\n if (store.set) store.set(name, '', { ...SESSION_COOKIE_ATTRS, maxAge: 0 });\n else store.delete?.(name);\n } catch {\n // RSC read-only store — cookie mutation is the middleware's job there\n }\n}\n\n/**\n * SessionStorageAdapter over the request cookie store.\n *\n * SAVE SEMANTICS (decision, documented): only FULL sessions (access token +\n * expiry present) are written; a refresh-only save is SKIPPED with no throw\n * rather than written as {a:'',e:0}. The cookie is SHARED with the browser —\n * degrading it would drop an access token the browser may still be using and\n * force a refresh per page-load. The refresh-only branch is a defensive\n * legacy path anyway: every successful refresh saves a full session.\n *\n * All writes swallow per-cookie failures: in RSC scope Next's store throws\n * on mutation; the request still proceeds with the in-memory session but the\n * rotated cookie is NOT persisted. palbeMiddleware is therefore REQUIRED —\n * not optional — for session-bearing RSC apps: without it every RSC render\n * re-refreshes from the SAME cookie refresh token, and once two such\n * refreshes land more than 30s apart (outside palauth's rotation grace) the\n * reuse detector REVOKES THE WHOLE TOKEN FAMILY — the user is\n * force-logged-out everywhere.\n *\n * Values are written DECODED (encodeSessionCookiesDecoded): Next's real\n * cookies() store percent-encodes on serialization, yielding exactly the\n * shared-codec wire bytes; a raw store (tests, custom servers) persists the\n * bare JSON, which the load path's decode tolerates identically (tokens are\n * %-free base64url, so decodeURIComponent is the identity on them).\n */\nfunction serverCookieAdapter(\n store: CookieStoreLike,\n environmentRef: string,\n): SessionStorageAdapter {\n const present = (name: string): boolean => store.get(name) !== undefined;\n return {\n load(): PersistedSession | null {\n const stored = decodeSessionCookies((name) => store.get(name)?.value, environmentRef);\n if (!stored) return null;\n // A browser-side refresh-only write round-trips as a:'' / e:0 —\n // normalize to the legacy shape so hydration takes the expired-trick.\n return stored.accessToken && stored.expiresAt > 0\n ? {\n refreshToken: stored.refreshToken,\n accessToken: stored.accessToken,\n expiresAt: stored.expiresAt,\n }\n : { refreshToken: stored.refreshToken };\n },\n save(session: PersistedSession): void {\n if (!session.accessToken || !session.expiresAt) return; // see SAVE SEMANTICS above\n for (const name of clearedSessionCookieNames(environmentRef, present)) {\n removeCookie(store, name);\n }\n const { set, clear } = encodeSessionCookiesDecoded(environmentRef, {\n accessToken: session.accessToken,\n refreshToken: session.refreshToken,\n expiresAt: session.expiresAt,\n });\n for (const { name, value } of set) {\n try {\n store.set?.(name, value, { ...SESSION_COOKIE_ATTRS });\n } catch {\n // RSC read-only store\n }\n }\n // Overflow guard (codec contract): delete one-past-the-end so a stale\n // orphan chunk behind a gap can never join a future chunk run.\n for (const name of clear) removeCookie(store, name);\n },\n clear(): void {\n for (const name of clearedSessionCookieNames(environmentRef, present)) {\n removeCookie(store, name);\n }\n },\n };\n}\n\n/**\n * Build a per-request `PB` client over the request's cookies. Reads the\n * session the browser SDK wrote (shared cookie codec), refreshes pre-flight\n * when expired, and — where the store is writable (Server Actions, Route\n * Handlers) — persists the rotated session back.\n *\n * Each call builds a fresh runtime (a handful of small objects, no I/O) —\n * cheap enough to call per data access; code paths invoking it many times\n * within one request MAY memoize per-request (e.g. React's `cache()`), but\n * don't have to.\n *\n * ```ts\n * const pb = await pbServer();\n * const todos = await pb.todos.list();\n * ```\n */\nexport async function pbServer(opts?: { cookies?: CookieStoreLike }): Promise<PB> {\n const config = requireGlobalConfig(\n 'import the generated palbe.gen.ts once in app/layout.tsx — the root-layout import configures Server Components and Route Handlers too.',\n );\n const store = opts?.cookies ?? (await nextCookieStore());\n const ref = environmentRefFromPublishableApiKey(config.apiKey);\n const rt = buildRuntime({ ...config, storage: serverCookieAdapter(store, ref) });\n return createBoundClient(rt);\n}\n"],"mappings":"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","names":["response"]}
@@ -1,4 +1,4 @@
1
- import { x as PalbeAuth, z as PalbeFlags, E as PalbeRealtime, w as PalbeAnalytics, y as PalbeCalls, B as PalbeMessaging, X as PalbePerf, d as PalbeRuntime } from './analytics-facade-DLfnVVwL.js';
1
+ import { x as PalbeAuth, z as PalbeFlags, E as PalbeRealtime, w as PalbeAnalytics, y as PalbeCalls, B as PalbeMessaging, X as PalbePerf, d as PalbeRuntime } from './analytics-facade-BER0EyYT.js';
2
2
 
3
3
  interface CallOptions {
4
4
  headers?: Record<string, string>;
@@ -1,4 +1,4 @@
1
- import { x as PalbeAuth, z as PalbeFlags, E as PalbeRealtime, w as PalbeAnalytics, y as PalbeCalls, B as PalbeMessaging, X as PalbePerf, d as PalbeRuntime } from './analytics-facade-jyXv7Cu4.cjs';
1
+ import { x as PalbeAuth, z as PalbeFlags, E as PalbeRealtime, w as PalbeAnalytics, y as PalbeCalls, B as PalbeMessaging, X as PalbePerf, d as PalbeRuntime } from './analytics-facade-CQXCQSoF.cjs';
2
2
 
3
3
  interface CallOptions {
4
4
  headers?: Record<string, string>;
@@ -169,6 +169,47 @@ function redactUrl(rawUrl) {
169
169
  return path.split("/").map((seg) => isSensitiveSegment(seg) ? ":id" : seg).join("/");
170
170
  }
171
171
 
172
+ // src/pow.ts
173
+ var POW_CHALLENGE_ID_HEADER = "X-PoW-Challenge-ID";
174
+ var POW_NONCE_HEADER = "X-PoW-Nonce";
175
+ function asPowChallenge(details) {
176
+ if (typeof details !== "object" || details === null) return null;
177
+ const env = details;
178
+ if (env.error !== "pow_required") return null;
179
+ const c = env.challenge;
180
+ if (typeof c !== "object" || c === null) return null;
181
+ const { id, prefix, difficulty } = c;
182
+ if (typeof id !== "string" || typeof prefix !== "string") return null;
183
+ if (typeof difficulty !== "number" || !Number.isInteger(difficulty) || difficulty < 0) return null;
184
+ return { id, prefix, difficulty };
185
+ }
186
+ var encoder = new TextEncoder();
187
+ function leadingZeroBits(hash) {
188
+ let bits = 0;
189
+ for (const byte of hash) {
190
+ if (byte === 0) {
191
+ bits += 8;
192
+ continue;
193
+ }
194
+ return bits + Math.clz32(byte) - 24;
195
+ }
196
+ return bits;
197
+ }
198
+ async function solvePowChallenge(challenge, maxIterations = 1 << 24) {
199
+ for (let nonce = 0; nonce < maxIterations; nonce++) {
200
+ const digest = await crypto.subtle.digest("SHA-256", encoder.encode(challenge.prefix + nonce));
201
+ if (leadingZeroBits(new Uint8Array(digest)) >= challenge.difficulty) {
202
+ return {
203
+ [POW_CHALLENGE_ID_HEADER]: challenge.id,
204
+ [POW_NONCE_HEADER]: String(nonce)
205
+ };
206
+ }
207
+ }
208
+ throw new Error(
209
+ `proof-of-work: no nonce found for difficulty ${challenge.difficulty} within ${maxIterations} attempts`
210
+ );
211
+ }
212
+
172
213
  // src/request.ts
173
214
  var MUTATING = /* @__PURE__ */ new Set(["POST", "PUT", "PATCH", "DELETE"]);
174
215
  var PERF_EXCLUDED_PREFIX = "/v1/analytics/";
@@ -192,11 +233,11 @@ async function palbeRequest(rt, method, path, spec = {}) {
192
233
  if (MUTATING.has(method) && !callerHasKey) {
193
234
  headers["Idempotency-Key"] = crypto.randomUUID();
194
235
  }
195
- const attempt = async () => {
236
+ const attempt = async (extra) => {
196
237
  try {
197
238
  return await rt.http.request(method, path, {
198
239
  body: spec.body,
199
- headers,
240
+ headers: extra ? { ...headers, ...extra } : headers,
200
241
  signal: spec.signal
201
242
  });
202
243
  } catch (e) {
@@ -231,6 +272,12 @@ async function palbeRequest(rt, method, path, spec = {}) {
231
272
  }
232
273
  res = await attempt();
233
274
  }
275
+ if (res.error?.status === 403 && res.error.code === "pow_required") {
276
+ const challenge = asPowChallenge(res.error.details);
277
+ if (challenge) {
278
+ res = await attempt(await solvePowChallenge(challenge));
279
+ }
280
+ }
234
281
  } catch (e) {
235
282
  if (!isAbort(e)) record(asPalbaseError(e)?.status ?? 0);
236
283
  throw e;
@@ -357,7 +404,7 @@ function applyBrowserOriginHeader(headers) {
357
404
  }
358
405
 
359
406
  // src/version.ts
360
- var VERSION = "7.2.1";
407
+ var VERSION = "7.3.0";
361
408
 
362
409
  // src/call.ts
363
410
  async function callEndpoint(resolveRt, name, input, options) {