@fanvue/builder-sdk 0.4.0 → 0.5.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 (39) hide show
  1. package/README.md +284 -0
  2. package/dist/bridge/index.d.ts +1 -1
  3. package/dist/bridge/index.js +2 -2
  4. package/dist/{bridge-CGVtI3hr.js → bridge-QnF7P4Co.js} +2 -2
  5. package/dist/{bridge-CGVtI3hr.js.map → bridge-QnF7P4Co.js.map} +1 -1
  6. package/dist/core/index.d.ts +2 -2
  7. package/dist/core/index.js +3 -3
  8. package/dist/{core-BqdYUMrJ.js → core-BhKiA55a.js} +1318 -9
  9. package/dist/core-BhKiA55a.js.map +1 -0
  10. package/dist/index-C3CXrRiw.d.ts +1412 -0
  11. package/dist/index-C3CXrRiw.d.ts.map +1 -0
  12. package/dist/{index-BRDYLBlc.d.ts → index-CEFYZtlb.d.ts} +2 -2
  13. package/dist/{index-BRDYLBlc.d.ts.map → index-CEFYZtlb.d.ts.map} +1 -1
  14. package/dist/{index-Dr-mZ0qP.d.ts → index-cf3ZMnLJ.d.ts} +26 -2
  15. package/dist/index-cf3ZMnLJ.d.ts.map +1 -0
  16. package/dist/index-v_6Z4Q0u.d.ts +84 -0
  17. package/dist/index-v_6Z4Q0u.d.ts.map +1 -0
  18. package/dist/{schemas-DbyHF7Xi.js → index.cjs-teGsk6HB.js} +1058 -977
  19. package/dist/index.cjs-teGsk6HB.js.map +1 -0
  20. package/dist/nextjs/embedded-app/index.d.ts +2 -2
  21. package/dist/nextjs/embedded-app/index.js +3 -3
  22. package/dist/nextjs/off-platform/index.d.ts +3 -3
  23. package/dist/nextjs/off-platform/index.d.ts.map +1 -1
  24. package/dist/nextjs/off-platform/index.js +4 -4
  25. package/dist/nextjs/off-platform/index.js.map +1 -1
  26. package/dist/nextjs-CvVhtMdb.js +144 -0
  27. package/dist/nextjs-CvVhtMdb.js.map +1 -0
  28. package/dist/react/index.d.ts +2 -2
  29. package/dist/react/index.js +3 -3
  30. package/package.json +1 -1
  31. package/dist/core-BqdYUMrJ.js.map +0 -1
  32. package/dist/index-C4ewLil3.d.ts +0 -48
  33. package/dist/index-C4ewLil3.d.ts.map +0 -1
  34. package/dist/index-ClbZoV_Z.d.ts +0 -420
  35. package/dist/index-ClbZoV_Z.d.ts.map +0 -1
  36. package/dist/index-Dr-mZ0qP.d.ts.map +0 -1
  37. package/dist/nextjs-B5Tqgt_n.js +0 -80
  38. package/dist/nextjs-B5Tqgt_n.js.map +0 -1
  39. package/dist/schemas-DbyHF7Xi.js.map +0 -1
@@ -1,5 +1,5 @@
1
- import { C as OAuthConfig, D as TokenResponse, T as SessionPayload, t as FanvueClient, u as getSessionTokenFromUrl, x as FanvueUser, y as EmbeddedAuthConfig } from "../../index-ClbZoV_Z.js";
2
- import { n as ResolvedConfig, t as FanvueAuthOptions } from "../../index-C4ewLil3.js";
1
+ import { H as EmbeddedAuthConfig, J as SessionPayload, K as OAuthConfig, N as getSessionTokenFromUrl, W as FanvueUser, X as TokenResponse, w as FanvueClient } from "../../index-C3CXrRiw.js";
2
+ import { n as FanvueAuthOptions, r as ResolvedConfig } from "../../index-v_6Z4Q0u.js";
3
3
  import { NextResponse } from "next/server";
4
4
 
5
5
  //#region src/nextjs/bearer-session.d.ts
@@ -1,6 +1,6 @@
1
- import { c as string, o as object } from "../../schemas-DbyHF7Xi.js";
2
- import { S as BEARER_PREFIX, n as exchangeSessionToken, o as createSessionJwt, r as getSessionTokenFromUrl, s as verifySessionJwt, t as createFanvueClient, u as refreshAccessToken } from "../../core-BqdYUMrJ.js";
3
- import { n as fetchUserProfile, o as LOG_PREFIX, t as createConfig$1 } from "../../nextjs-B5Tqgt_n.js";
1
+ import { f as string, l as object } from "../../index.cjs-teGsk6HB.js";
2
+ import { _ as createSessionJwt, bt as BEARER_PREFIX, f as createFanvueClient, m as getSessionTokenFromUrl, p as exchangeSessionToken, v as verifySessionJwt, x as refreshAccessToken } from "../../core-BhKiA55a.js";
3
+ import { r as fetchUserProfile, s as LOG_PREFIX, t as createConfig$1 } from "../../nextjs-CvVhtMdb.js";
4
4
  import { NextResponse } from "next/server";
5
5
  import { headers } from "next/headers";
6
6
  //#region src/nextjs/bearer-session.ts
@@ -1,5 +1,5 @@
1
- import { C as OAuthConfig, T as SessionPayload, t as FanvueClient } from "../../index-ClbZoV_Z.js";
2
- import { n as ResolvedConfig, r as createConfig, t as FanvueAuthOptions } from "../../index-C4ewLil3.js";
1
+ import { J as SessionPayload, K as OAuthConfig, w as FanvueClient } from "../../index-C3CXrRiw.js";
2
+ import { a as createConfigSafe, i as createConfig, n as FanvueAuthOptions, r as ResolvedConfig, t as CreateConfigResult } from "../../index-v_6Z4Q0u.js";
3
3
  import { NextResponse } from "next/server";
4
4
 
5
5
  //#region src/nextjs/off-platform/session.d.ts
@@ -70,5 +70,5 @@ declare function createLogoutHandler(opts: OffPlatformOptions): {
70
70
  POST(request: Request): NextResponse;
71
71
  };
72
72
  //#endregion
73
- export { type FanvueAuthOptions, OffPlatformOptions, type ResolvedConfig, createCallbackHandler, createConfig, createLoginHandler, createLogoutHandler, getAuthenticatedClient, getSession };
73
+ export { type CreateConfigResult, type FanvueAuthOptions, OffPlatformOptions, type ResolvedConfig, createCallbackHandler, createConfig, createConfigSafe, createLoginHandler, createLogoutHandler, getAuthenticatedClient, getSession };
74
74
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../../../src/nextjs/off-platform/session.ts","../../../src/nextjs/off-platform/index.ts"],"mappings":";;;;;;;;;AA2BA;;;iBAAsB,UAAA,CACpB,MAAA,UACA,UAAA,mBACC,OAAA,CAAQ,cAAA;;;;;;;;;;;iBAuDW,sBAAA,CAAuB,IAAA;EAC3C,aAAA;EACA,iBAAA;EACA,MAAA,EAAQ,WAAA;AAAA,IACN,OAAA,CAAQ,YAAA;;;;AA9DZ;;;;KCLY,kBAAA,GAAqB,cAAA;EAC/B,cAAA;EACA,eAAA;AAAA;;;AD6DF;;;;iBCpDgB,kBAAA,CAAmB,IAAA,EAAM,kBAAA;EACvC,GAAA,CAAI,OAAA,EAAS,OAAA,GAAU,OAAA,CAAQ,YAAA;AAAA;;;;;;;;;;iBAcjB,qBAAA,CAAsB,IAAA,EAAM,kBAAA;EAC1C,GAAA,CAAI,OAAA,EAAS,OAAA,GAAU,OAAA,CAAQ,YAAA;EAC/B,IAAA,CAAK,OAAA,EAAS,OAAA,GAAU,OAAA,CAAQ,YAAA;AAAA;AA5BlC;;;;;;;AAAA,iBAoEgB,mBAAA,CAAoB,IAAA,EAAM,kBAAA;EACxC,IAAA,CAAK,OAAA,EAAS,OAAA,GAAU,YAAA;AAAA"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../../src/nextjs/off-platform/session.ts","../../../src/nextjs/off-platform/index.ts"],"mappings":";;;;;;;;;AA2BA;;;iBAAsB,UAAA,CACpB,MAAA,UACA,UAAA,mBACC,OAAA,CAAQ,cAAA;;;;;;;;;;;iBAuDW,sBAAA,CAAuB,IAAA;EAC3C,aAAA;EACA,iBAAA;EACA,MAAA,EAAQ,WAAA;AAAA,IACN,OAAA,CAAQ,YAAA;;;AA9DZ;;;;;AAAA,KCJY,kBAAA,GAAqB,cAAA;EAC/B,cAAA;EACA,eAAA;AAAA;;AD4DF;;;;;iBCnDgB,kBAAA,CAAmB,IAAA,EAAM,kBAAA;EACvC,GAAA,CAAI,OAAA,EAAS,OAAA,GAAU,OAAA,CAAQ,YAAA;AAAA;;;;;;;;;;iBAcjB,qBAAA,CAAsB,IAAA,EAAM,kBAAA;EAC1C,GAAA,CAAI,OAAA,EAAS,OAAA,GAAU,OAAA,CAAQ,YAAA;EAC/B,IAAA,CAAK,OAAA,EAAS,OAAA,GAAU,OAAA,CAAQ,YAAA;AAAA;;;;;;;;iBAwClB,mBAAA,CAAoB,IAAA,EAAM,kBAAA;EACxC,IAAA,CAAK,OAAA,EAAS,OAAA,GAAU,YAAA;AAAA"}
@@ -1,6 +1,6 @@
1
- import "../../schemas-DbyHF7Xi.js";
2
- import { c as createAuthorizationUrl, l as exchangeCodeForToken, o as createSessionJwt, s as verifySessionJwt, t as createFanvueClient, u as refreshAccessToken } from "../../core-BqdYUMrJ.js";
3
- import { i as getFormString, n as fetchUserProfile, o as LOG_PREFIX, r as getBaseUrl, s as SESSION_COOKIE_MAX_AGE_SECONDS, t as createConfig } from "../../nextjs-B5Tqgt_n.js";
1
+ import "../../index.cjs-teGsk6HB.js";
2
+ import { _ as createSessionJwt, b as exchangeCodeForToken, f as createFanvueClient, v as verifySessionJwt, x as refreshAccessToken, y as createAuthorizationUrl } from "../../core-BhKiA55a.js";
3
+ import { a as getFormString, c as SESSION_COOKIE_MAX_AGE_SECONDS, i as getBaseUrl, n as createConfigSafe, r as fetchUserProfile, s as LOG_PREFIX, t as createConfig } from "../../nextjs-CvVhtMdb.js";
4
4
  import { NextResponse } from "next/server";
5
5
  import { cookies } from "next/headers";
6
6
  //#region src/nextjs/callback.ts
@@ -253,6 +253,6 @@ function createLogoutHandler(opts) {
253
253
  } };
254
254
  }
255
255
  //#endregion
256
- export { createCallbackHandler, createConfig, createLoginHandler, createLogoutHandler, getAuthenticatedClient, getSession };
256
+ export { createCallbackHandler, createConfig, createConfigSafe, createLoginHandler, createLogoutHandler, getAuthenticatedClient, getSession };
257
257
 
258
258
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["createLoginHandler","createSharedLoginHandler"],"sources":["../../../src/nextjs/callback.ts","../../../src/nextjs/off-platform/session.ts","../../../src/nextjs/off-platform/index.ts"],"sourcesContent":["import { cookies } from 'next/headers';\nimport { NextResponse } from 'next/server';\n\nimport {\n createAuthorizationUrl,\n exchangeCodeForToken,\n createSessionJwt,\n type SessionPayload,\n} from '../core/index.js';\n\nimport { LOG_PREFIX, COOKIE_SAME_SITE } from './constants.js';\nimport { fetchUserProfile, getBaseUrl } from './utils.js';\n\nimport type { ResolvedConfig } from './index.js';\n\nconst COOKIE_OAUTH_STATE = 'oauth_state';\nconst COOKIE_OAUTH_VERIFIER = 'oauth_verifier';\nconst OAUTH_COOKIE_MAX_AGE_SECONDS = 600;\nconst ERROR_STATE_MISMATCH = 'oauth_state_mismatch';\nconst ERROR_TOKEN_EXCHANGE_FAILED = 'oauth_token_exchange_failed';\nconst ERROR_USER_PROFILE_FAILED = 'oauth_user_profile_failed';\n\n/** Parameters extracted from the OAuth callback request. */\nexport type CallbackParams =\n | { kind: 'success'; code: string; state: string }\n | { kind: 'error'; error: string; errorDescription: string | null }\n | { kind: 'empty' };\n\n/** Strategy for completing the OAuth callback — how the JWT is delivered to the client. */\nexport interface CallbackStrategy {\n complete(request: Request, jwt: string, baseUrl: string): NextResponse;\n}\n\n/** Options required by the shared callback handler. */\nexport type CallbackHandlerOptions = ResolvedConfig;\n\n/**\n * Shared callback handler that validates the OAuth state, exchanges the\n * authorization code for tokens, fetches the user profile, creates a session\n * JWT, and delegates to the strategy for final response.\n */\nexport async function handleOAuthCallback(\n request: Request,\n opts: CallbackHandlerOptions,\n getParams: (request: Request) => Promise<CallbackParams>,\n strategy: CallbackStrategy,\n): Promise<NextResponse> {\n const baseUrl = getBaseUrl(opts, request.url);\n const params = await getParams(request);\n\n const cookieStore = await cookies();\n const storedState = cookieStore.get(COOKIE_OAUTH_STATE)?.value;\n const verifier = cookieStore.get(COOKIE_OAUTH_VERIFIER)?.value;\n\n cookieStore.delete(COOKIE_OAUTH_STATE);\n cookieStore.delete(COOKIE_OAUTH_VERIFIER);\n\n if (params.kind === 'error') {\n const search = new URLSearchParams();\n search.set('error', params.error);\n if (params.errorDescription) search.set('error_description', params.errorDescription);\n return NextResponse.redirect(new URL(`${baseUrl}/?${search.toString()}`, request.url));\n }\n\n if (params.kind === 'empty' || !storedState || !verifier || params.state !== storedState) {\n return NextResponse.redirect(new URL(`${baseUrl}/?error=${ERROR_STATE_MISMATCH}`, request.url));\n }\n\n const { code } = params;\n\n const tokenResult = await exchangeCodeForToken(opts, {\n code,\n codeVerifier: verifier,\n redirectUri: null,\n });\n\n if (tokenResult.isErr()) {\n console.error(`${LOG_PREFIX} token exchange failed:`, tokenResult.error);\n return NextResponse.redirect(\n new URL(`${baseUrl}/?error=${ERROR_TOKEN_EXCHANGE_FAILED}`, request.url),\n );\n }\n\n const token = tokenResult.value;\n const user = await fetchUserProfile(token.access_token, opts.apiBaseUrl);\n\n if (!user) {\n return NextResponse.redirect(\n new URL(`${baseUrl}/?error=${ERROR_USER_PROFILE_FAILED}`, request.url),\n );\n }\n\n const sessionPayload: SessionPayload = {\n accessToken: token.access_token,\n refreshToken: token.refresh_token,\n tokenType: token.token_type,\n scope: token.scope,\n idToken: token.id_token,\n expiresAt: Date.now() + token.expires_in * 1000,\n userUuid: user.uuid,\n handle: user.handle,\n displayName: user.displayName,\n isCreator: user.isCreator,\n avatarUrl: user.avatarUrl,\n };\n\n const jwt = await createSessionJwt(opts.sessionSecret, sessionPayload);\n\n return strategy.complete(request, jwt, baseUrl);\n}\n\n/**\n * Creates a Next.js route handler that initiates the OAuth login flow.\n * Used by the off-platform module.\n */\nexport function createLoginHandler(opts: CallbackHandlerOptions): {\n GET(request: Request): Promise<NextResponse>;\n} {\n return {\n async GET(request: Request): Promise<NextResponse> {\n const { url, codeVerifier, state } = await createAuthorizationUrl(opts);\n\n const cookieStore = await cookies();\n const reqUrl = new URL(request.url);\n const secure = reqUrl.protocol === 'https:';\n\n cookieStore.set(COOKIE_OAUTH_STATE, state, {\n httpOnly: true,\n path: '/',\n sameSite: COOKIE_SAME_SITE,\n secure,\n maxAge: OAUTH_COOKIE_MAX_AGE_SECONDS,\n });\n cookieStore.set(COOKIE_OAUTH_VERIFIER, codeVerifier, {\n httpOnly: true,\n path: '/',\n sameSite: COOKIE_SAME_SITE,\n secure,\n maxAge: OAUTH_COOKIE_MAX_AGE_SECONDS,\n });\n\n return NextResponse.redirect(url);\n },\n };\n}\n\n/** Extracts callback params from a GET request's query string. */\nexport function getParamsFromQuery(request: Request): Promise<CallbackParams> {\n const url = new URL(request.url);\n const error = url.searchParams.get('error');\n if (error) {\n return Promise.resolve({\n kind: 'error',\n error,\n errorDescription: url.searchParams.get('error_description'),\n });\n }\n const code = url.searchParams.get('code');\n const state = url.searchParams.get('state');\n if (code && state) {\n return Promise.resolve({ kind: 'success', code, state });\n }\n return Promise.resolve({ kind: 'empty' });\n}\n\n/** Extracts callback params from a POST request's form body. */\nexport async function getParamsFromForm(\n request: Request,\n getFormString: (form: FormData, key: string) => string | null,\n): Promise<CallbackParams> {\n const form = await request.formData();\n const error = getFormString(form, 'error');\n if (error) {\n return {\n kind: 'error',\n error,\n errorDescription: getFormString(form, 'error_description'),\n };\n }\n const code = getFormString(form, 'code');\n const state = getFormString(form, 'state');\n if (code && state) {\n return { kind: 'success', code, state };\n }\n return { kind: 'empty' };\n}\n","import { cookies } from 'next/headers';\n\nimport {\n verifySessionJwt,\n createSessionJwt,\n createFanvueClient,\n refreshAccessToken,\n type FanvueClient,\n type OAuthConfig,\n type SessionPayload,\n} from '../../core/index.js';\nimport {\n DEFAULT_SESSION_COOKIE_NAME,\n SESSION_COOKIE_MAX_AGE_SECONDS,\n COOKIE_SAME_SITE,\n LOG_PREFIX,\n} from '../constants.js';\n\nconst TOKEN_REFRESH_THRESHOLD_MS = 30_000;\n\n/**\n * Reads and verifies the session JWT from the cookie store.\n *\n * @param secret - The secret used to verify the JWT.\n * @param cookieName - The cookie name to read. Defaults to {@link DEFAULT_SESSION_COOKIE_NAME}.\n * @returns The verified {@link SessionPayload}, or `null` if missing/invalid.\n */\nexport async function getSession(\n secret: string,\n cookieName: string | null = null,\n): Promise<SessionPayload | null> {\n const cookieStore = await cookies();\n const token = cookieStore.get(cookieName ?? DEFAULT_SESSION_COOKIE_NAME)?.value;\n if (!token) return null;\n const result = await verifySessionJwt(secret, token);\n if (result.isErr()) {\n console.warn(`${LOG_PREFIX} session verify failed:`, result.error);\n return null;\n }\n return result.value;\n}\n\n/**\n * Creates or updates the session cookie with a fresh JWT.\n *\n * @param secret - The secret used to sign the JWT.\n * @param cookieName - The cookie name to set.\n * @param payload - The session data to encode into the JWT.\n */\nexport async function setSession(\n secret: string,\n cookieName: string,\n payload: SessionPayload,\n): Promise<void> {\n const jwt = await createSessionJwt(secret, payload);\n const cookieStore = await cookies();\n cookieStore.set(cookieName, jwt, {\n httpOnly: true,\n secure: process.env['NODE_ENV'] === 'production',\n sameSite: COOKIE_SAME_SITE,\n path: '/',\n maxAge: SESSION_COOKIE_MAX_AGE_SECONDS,\n });\n}\n\n/**\n * Deletes the session cookie.\n *\n * @param cookieName - The cookie name to delete.\n */\nexport async function clearSession(cookieName: string): Promise<void> {\n const cookieStore = await cookies();\n cookieStore.delete(cookieName);\n}\n\n/**\n * Returns an authenticated {@link FanvueClient} for the current session.\n *\n * If the access token is close to expiring and a refresh token is available,\n * the token is refreshed transparently and the session cookie is updated.\n *\n * @param opts - Session secret, cookie name, and OAuth config.\n * @returns A {@link FanvueClient} instance, or `null` if no valid session exists\n * or token refresh fails.\n */\nexport async function getAuthenticatedClient(opts: {\n sessionSecret: string;\n sessionCookieName: string | null;\n config: OAuthConfig;\n}): Promise<FanvueClient | null> {\n const cookieName = opts.sessionCookieName ?? DEFAULT_SESSION_COOKIE_NAME;\n let session = await getSession(opts.sessionSecret, cookieName);\n if (!session) return null;\n\n if (Date.now() >= session.expiresAt - TOKEN_REFRESH_THRESHOLD_MS && session.refreshToken) {\n const refreshResult = await refreshAccessToken(opts.config, session.refreshToken);\n\n if (refreshResult.isErr()) {\n console.warn(`${LOG_PREFIX} token refresh failed:`, refreshResult.error);\n return null;\n }\n\n const refreshed = refreshResult.value;\n // When the refresh response omits optional fields (parsed as null by the schema),\n // preserve the existing session values. Per RFC 6749 §6, a refresh response MAY\n // include a new refresh_token; when absent, the existing one remains valid.\n session = {\n ...session,\n accessToken: refreshed.access_token,\n refreshToken: refreshed.refresh_token ?? session.refreshToken,\n tokenType: refreshed.token_type ?? session.tokenType,\n scope: refreshed.scope ?? session.scope,\n idToken: refreshed.id_token ?? session.idToken,\n expiresAt: Date.now() + refreshed.expires_in * 1000,\n };\n try {\n await setSession(opts.sessionSecret, cookieName, session);\n } catch (error) {\n console.warn(`${LOG_PREFIX} failed to persist refreshed session:`, error);\n }\n }\n\n return createFanvueClient(session.accessToken, opts.config.apiBaseUrl);\n}\n","import { NextResponse } from 'next/server';\n\nimport {\n handleOAuthCallback,\n createLoginHandler as createSharedLoginHandler,\n getParamsFromQuery,\n getParamsFromForm,\n} from '../callback.js';\nimport { COOKIE_SAME_SITE, SESSION_COOKIE_MAX_AGE_SECONDS } from '../constants.js';\nimport { type ResolvedConfig } from '../index.js';\nimport { getBaseUrl, getFormString } from '../utils.js';\n\nimport type { CallbackStrategy } from '../callback.js';\n\nexport { getSession, getAuthenticatedClient } from './session.js';\nexport { createConfig, type FanvueAuthOptions, type ResolvedConfig } from '../index.js';\n\n/**\n * Configuration for the off-platform OAuth handlers.\n *\n * Extends {@link ResolvedConfig} with optional routing overrides.\n */\nexport type OffPlatformOptions = ResolvedConfig & {\n afterLoginPath: string | null;\n afterLogoutPath: string | null;\n};\n\n/**\n * Creates a Next.js route handler that initiates the OAuth login flow.\n *\n * @param opts - The off-platform configuration.\n * @returns A route handler object with a `GET` method.\n */\nexport function createLoginHandler(opts: OffPlatformOptions): {\n GET(request: Request): Promise<NextResponse>;\n} {\n return createSharedLoginHandler(opts);\n}\n\n/**\n * Creates a Next.js route handler that processes the OAuth callback.\n *\n * Supports both `GET` (query-string params) and `POST` (form-encoded body)\n * response modes.\n *\n * @param opts - The off-platform configuration.\n * @returns A route handler object with `GET` and `POST` methods.\n */\nexport function createCallbackHandler(opts: OffPlatformOptions): {\n GET(request: Request): Promise<NextResponse>;\n POST(request: Request): Promise<NextResponse>;\n} {\n const strategy: CallbackStrategy = {\n complete(request: Request, jwt: string, baseUrl: string): NextResponse {\n const res = NextResponse.redirect(\n new URL(`${baseUrl}${opts.afterLoginPath ?? '/'}`, request.url),\n );\n res.cookies.set(opts.sessionCookieName, jwt, {\n httpOnly: true,\n secure: new URL(request.url).protocol === 'https:',\n sameSite: COOKIE_SAME_SITE,\n path: '/',\n maxAge: SESSION_COOKIE_MAX_AGE_SECONDS,\n });\n return res;\n },\n };\n\n return {\n async GET(request: Request): Promise<NextResponse> {\n return handleOAuthCallback(request, opts, getParamsFromQuery, strategy);\n },\n async POST(request: Request): Promise<NextResponse> {\n return handleOAuthCallback(\n request,\n opts,\n (req) => getParamsFromForm(req, getFormString),\n strategy,\n );\n },\n };\n}\n\n/**\n * Creates a Next.js route handler that logs the user out by deleting the\n * session cookie and redirecting.\n *\n * @param opts - The off-platform configuration.\n * @returns A route handler object with a `POST` method.\n */\nexport function createLogoutHandler(opts: OffPlatformOptions): {\n POST(request: Request): NextResponse;\n} {\n return {\n POST(request: Request): NextResponse {\n const baseUrl = getBaseUrl(opts, request.url);\n const res = NextResponse.redirect(\n new URL(`${baseUrl}${opts.afterLogoutPath ?? '/'}`, request.url),\n );\n res.cookies.delete(opts.sessionCookieName);\n return res;\n },\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["createLoginHandler","createSharedLoginHandler"],"sources":["../../../src/nextjs/callback.ts","../../../src/nextjs/off-platform/session.ts","../../../src/nextjs/off-platform/index.ts"],"sourcesContent":["import { cookies } from 'next/headers';\nimport { NextResponse } from 'next/server';\n\nimport {\n createAuthorizationUrl,\n exchangeCodeForToken,\n createSessionJwt,\n type SessionPayload,\n} from '../core/index.js';\n\nimport { LOG_PREFIX, COOKIE_SAME_SITE } from './constants.js';\nimport { fetchUserProfile, getBaseUrl } from './utils.js';\n\nimport type { ResolvedConfig } from './index.js';\n\nconst COOKIE_OAUTH_STATE = 'oauth_state';\nconst COOKIE_OAUTH_VERIFIER = 'oauth_verifier';\nconst OAUTH_COOKIE_MAX_AGE_SECONDS = 600;\nconst ERROR_STATE_MISMATCH = 'oauth_state_mismatch';\nconst ERROR_TOKEN_EXCHANGE_FAILED = 'oauth_token_exchange_failed';\nconst ERROR_USER_PROFILE_FAILED = 'oauth_user_profile_failed';\n\n/** Parameters extracted from the OAuth callback request. */\nexport type CallbackParams =\n | { kind: 'success'; code: string; state: string }\n | { kind: 'error'; error: string; errorDescription: string | null }\n | { kind: 'empty' };\n\n/** Strategy for completing the OAuth callback — how the JWT is delivered to the client. */\nexport interface CallbackStrategy {\n complete(request: Request, jwt: string, baseUrl: string): NextResponse;\n}\n\n/** Options required by the shared callback handler. */\nexport type CallbackHandlerOptions = ResolvedConfig;\n\n/**\n * Shared callback handler that validates the OAuth state, exchanges the\n * authorization code for tokens, fetches the user profile, creates a session\n * JWT, and delegates to the strategy for final response.\n */\nexport async function handleOAuthCallback(\n request: Request,\n opts: CallbackHandlerOptions,\n getParams: (request: Request) => Promise<CallbackParams>,\n strategy: CallbackStrategy,\n): Promise<NextResponse> {\n const baseUrl = getBaseUrl(opts, request.url);\n const params = await getParams(request);\n\n const cookieStore = await cookies();\n const storedState = cookieStore.get(COOKIE_OAUTH_STATE)?.value;\n const verifier = cookieStore.get(COOKIE_OAUTH_VERIFIER)?.value;\n\n cookieStore.delete(COOKIE_OAUTH_STATE);\n cookieStore.delete(COOKIE_OAUTH_VERIFIER);\n\n if (params.kind === 'error') {\n const search = new URLSearchParams();\n search.set('error', params.error);\n if (params.errorDescription) search.set('error_description', params.errorDescription);\n return NextResponse.redirect(new URL(`${baseUrl}/?${search.toString()}`, request.url));\n }\n\n if (params.kind === 'empty' || !storedState || !verifier || params.state !== storedState) {\n return NextResponse.redirect(new URL(`${baseUrl}/?error=${ERROR_STATE_MISMATCH}`, request.url));\n }\n\n const { code } = params;\n\n const tokenResult = await exchangeCodeForToken(opts, {\n code,\n codeVerifier: verifier,\n redirectUri: null,\n });\n\n if (tokenResult.isErr()) {\n console.error(`${LOG_PREFIX} token exchange failed:`, tokenResult.error);\n return NextResponse.redirect(\n new URL(`${baseUrl}/?error=${ERROR_TOKEN_EXCHANGE_FAILED}`, request.url),\n );\n }\n\n const token = tokenResult.value;\n const user = await fetchUserProfile(token.access_token, opts.apiBaseUrl);\n\n if (!user) {\n return NextResponse.redirect(\n new URL(`${baseUrl}/?error=${ERROR_USER_PROFILE_FAILED}`, request.url),\n );\n }\n\n const sessionPayload: SessionPayload = {\n accessToken: token.access_token,\n refreshToken: token.refresh_token,\n tokenType: token.token_type,\n scope: token.scope,\n idToken: token.id_token,\n expiresAt: Date.now() + token.expires_in * 1000,\n userUuid: user.uuid,\n handle: user.handle,\n displayName: user.displayName,\n isCreator: user.isCreator,\n avatarUrl: user.avatarUrl,\n };\n\n const jwt = await createSessionJwt(opts.sessionSecret, sessionPayload);\n\n return strategy.complete(request, jwt, baseUrl);\n}\n\n/**\n * Creates a Next.js route handler that initiates the OAuth login flow.\n * Used by the off-platform module.\n */\nexport function createLoginHandler(opts: CallbackHandlerOptions): {\n GET(request: Request): Promise<NextResponse>;\n} {\n return {\n async GET(request: Request): Promise<NextResponse> {\n const { url, codeVerifier, state } = await createAuthorizationUrl(opts);\n\n const cookieStore = await cookies();\n const reqUrl = new URL(request.url);\n const secure = reqUrl.protocol === 'https:';\n\n cookieStore.set(COOKIE_OAUTH_STATE, state, {\n httpOnly: true,\n path: '/',\n sameSite: COOKIE_SAME_SITE,\n secure,\n maxAge: OAUTH_COOKIE_MAX_AGE_SECONDS,\n });\n cookieStore.set(COOKIE_OAUTH_VERIFIER, codeVerifier, {\n httpOnly: true,\n path: '/',\n sameSite: COOKIE_SAME_SITE,\n secure,\n maxAge: OAUTH_COOKIE_MAX_AGE_SECONDS,\n });\n\n return NextResponse.redirect(url);\n },\n };\n}\n\n/** Extracts callback params from a GET request's query string. */\nexport function getParamsFromQuery(request: Request): Promise<CallbackParams> {\n const url = new URL(request.url);\n const error = url.searchParams.get('error');\n if (error) {\n return Promise.resolve({\n kind: 'error',\n error,\n errorDescription: url.searchParams.get('error_description'),\n });\n }\n const code = url.searchParams.get('code');\n const state = url.searchParams.get('state');\n if (code && state) {\n return Promise.resolve({ kind: 'success', code, state });\n }\n return Promise.resolve({ kind: 'empty' });\n}\n\n/** Extracts callback params from a POST request's form body. */\nexport async function getParamsFromForm(\n request: Request,\n getFormString: (form: FormData, key: string) => string | null,\n): Promise<CallbackParams> {\n const form = await request.formData();\n const error = getFormString(form, 'error');\n if (error) {\n return {\n kind: 'error',\n error,\n errorDescription: getFormString(form, 'error_description'),\n };\n }\n const code = getFormString(form, 'code');\n const state = getFormString(form, 'state');\n if (code && state) {\n return { kind: 'success', code, state };\n }\n return { kind: 'empty' };\n}\n","import { cookies } from 'next/headers';\n\nimport {\n verifySessionJwt,\n createSessionJwt,\n createFanvueClient,\n refreshAccessToken,\n type FanvueClient,\n type OAuthConfig,\n type SessionPayload,\n} from '../../core/index.js';\nimport {\n DEFAULT_SESSION_COOKIE_NAME,\n SESSION_COOKIE_MAX_AGE_SECONDS,\n COOKIE_SAME_SITE,\n LOG_PREFIX,\n} from '../constants.js';\n\nconst TOKEN_REFRESH_THRESHOLD_MS = 30_000;\n\n/**\n * Reads and verifies the session JWT from the cookie store.\n *\n * @param secret - The secret used to verify the JWT.\n * @param cookieName - The cookie name to read. Defaults to {@link DEFAULT_SESSION_COOKIE_NAME}.\n * @returns The verified {@link SessionPayload}, or `null` if missing/invalid.\n */\nexport async function getSession(\n secret: string,\n cookieName: string | null = null,\n): Promise<SessionPayload | null> {\n const cookieStore = await cookies();\n const token = cookieStore.get(cookieName ?? DEFAULT_SESSION_COOKIE_NAME)?.value;\n if (!token) return null;\n const result = await verifySessionJwt(secret, token);\n if (result.isErr()) {\n console.warn(`${LOG_PREFIX} session verify failed:`, result.error);\n return null;\n }\n return result.value;\n}\n\n/**\n * Creates or updates the session cookie with a fresh JWT.\n *\n * @param secret - The secret used to sign the JWT.\n * @param cookieName - The cookie name to set.\n * @param payload - The session data to encode into the JWT.\n */\nexport async function setSession(\n secret: string,\n cookieName: string,\n payload: SessionPayload,\n): Promise<void> {\n const jwt = await createSessionJwt(secret, payload);\n const cookieStore = await cookies();\n cookieStore.set(cookieName, jwt, {\n httpOnly: true,\n secure: process.env['NODE_ENV'] === 'production',\n sameSite: COOKIE_SAME_SITE,\n path: '/',\n maxAge: SESSION_COOKIE_MAX_AGE_SECONDS,\n });\n}\n\n/**\n * Deletes the session cookie.\n *\n * @param cookieName - The cookie name to delete.\n */\nexport async function clearSession(cookieName: string): Promise<void> {\n const cookieStore = await cookies();\n cookieStore.delete(cookieName);\n}\n\n/**\n * Returns an authenticated {@link FanvueClient} for the current session.\n *\n * If the access token is close to expiring and a refresh token is available,\n * the token is refreshed transparently and the session cookie is updated.\n *\n * @param opts - Session secret, cookie name, and OAuth config.\n * @returns A {@link FanvueClient} instance, or `null` if no valid session exists\n * or token refresh fails.\n */\nexport async function getAuthenticatedClient(opts: {\n sessionSecret: string;\n sessionCookieName: string | null;\n config: OAuthConfig;\n}): Promise<FanvueClient | null> {\n const cookieName = opts.sessionCookieName ?? DEFAULT_SESSION_COOKIE_NAME;\n let session = await getSession(opts.sessionSecret, cookieName);\n if (!session) return null;\n\n if (Date.now() >= session.expiresAt - TOKEN_REFRESH_THRESHOLD_MS && session.refreshToken) {\n const refreshResult = await refreshAccessToken(opts.config, session.refreshToken);\n\n if (refreshResult.isErr()) {\n console.warn(`${LOG_PREFIX} token refresh failed:`, refreshResult.error);\n return null;\n }\n\n const refreshed = refreshResult.value;\n // When the refresh response omits optional fields (parsed as null by the schema),\n // preserve the existing session values. Per RFC 6749 §6, a refresh response MAY\n // include a new refresh_token; when absent, the existing one remains valid.\n session = {\n ...session,\n accessToken: refreshed.access_token,\n refreshToken: refreshed.refresh_token ?? session.refreshToken,\n tokenType: refreshed.token_type ?? session.tokenType,\n scope: refreshed.scope ?? session.scope,\n idToken: refreshed.id_token ?? session.idToken,\n expiresAt: Date.now() + refreshed.expires_in * 1000,\n };\n try {\n await setSession(opts.sessionSecret, cookieName, session);\n } catch (error) {\n console.warn(`${LOG_PREFIX} failed to persist refreshed session:`, error);\n }\n }\n\n return createFanvueClient(session.accessToken, opts.config.apiBaseUrl);\n}\n","import { NextResponse } from 'next/server';\n\nimport {\n handleOAuthCallback,\n createLoginHandler as createSharedLoginHandler,\n getParamsFromQuery,\n getParamsFromForm,\n} from '../callback.js';\nimport { COOKIE_SAME_SITE, SESSION_COOKIE_MAX_AGE_SECONDS } from '../constants.js';\nimport { type ResolvedConfig } from '../index.js';\nimport { getBaseUrl, getFormString } from '../utils.js';\n\nimport type { CallbackStrategy } from '../callback.js';\n\nexport { getSession, getAuthenticatedClient } from './session.js';\nexport { createConfig, type FanvueAuthOptions, type ResolvedConfig } from '../index.js';\nexport { createConfigSafe, type CreateConfigResult } from '../index.js';\n\n/**\n * Configuration for the off-platform OAuth handlers.\n *\n * Extends {@link ResolvedConfig} with optional routing overrides.\n */\nexport type OffPlatformOptions = ResolvedConfig & {\n afterLoginPath: string | null;\n afterLogoutPath: string | null;\n};\n\n/**\n * Creates a Next.js route handler that initiates the OAuth login flow.\n *\n * @param opts - The off-platform configuration.\n * @returns A route handler object with a `GET` method.\n */\nexport function createLoginHandler(opts: OffPlatformOptions): {\n GET(request: Request): Promise<NextResponse>;\n} {\n return createSharedLoginHandler(opts);\n}\n\n/**\n * Creates a Next.js route handler that processes the OAuth callback.\n *\n * Supports both `GET` (query-string params) and `POST` (form-encoded body)\n * response modes.\n *\n * @param opts - The off-platform configuration.\n * @returns A route handler object with `GET` and `POST` methods.\n */\nexport function createCallbackHandler(opts: OffPlatformOptions): {\n GET(request: Request): Promise<NextResponse>;\n POST(request: Request): Promise<NextResponse>;\n} {\n const strategy: CallbackStrategy = {\n complete(request: Request, jwt: string, baseUrl: string): NextResponse {\n const res = NextResponse.redirect(\n new URL(`${baseUrl}${opts.afterLoginPath ?? '/'}`, request.url),\n );\n res.cookies.set(opts.sessionCookieName, jwt, {\n httpOnly: true,\n secure: new URL(request.url).protocol === 'https:',\n sameSite: COOKIE_SAME_SITE,\n path: '/',\n maxAge: SESSION_COOKIE_MAX_AGE_SECONDS,\n });\n return res;\n },\n };\n\n return {\n async GET(request: Request): Promise<NextResponse> {\n return handleOAuthCallback(request, opts, getParamsFromQuery, strategy);\n },\n async POST(request: Request): Promise<NextResponse> {\n return handleOAuthCallback(\n request,\n opts,\n (req) => getParamsFromForm(req, getFormString),\n strategy,\n );\n },\n };\n}\n\n/**\n * Creates a Next.js route handler that logs the user out by deleting the\n * session cookie and redirecting.\n *\n * @param opts - The off-platform configuration.\n * @returns A route handler object with a `POST` method.\n */\nexport function createLogoutHandler(opts: OffPlatformOptions): {\n POST(request: Request): NextResponse;\n} {\n return {\n POST(request: Request): NextResponse {\n const baseUrl = getBaseUrl(opts, request.url);\n const res = NextResponse.redirect(\n new URL(`${baseUrl}${opts.afterLogoutPath ?? '/'}`, request.url),\n );\n res.cookies.delete(opts.sessionCookieName);\n return res;\n },\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,144 @@
1
+ import { f as createFanvueClient, mt as assertFanvueDomain } from "./core-BhKiA55a.js";
2
+ //#region src/nextjs/constants.ts
3
+ /** Default name for the session cookie. */
4
+ const DEFAULT_SESSION_COOKIE_NAME = "fanvue_session";
5
+ /** Session cookie max-age in seconds (30 days). */
6
+ const SESSION_COOKIE_MAX_AGE_SECONDS = 3600 * 24 * 30;
7
+ /** Log prefix for all library log messages. */
8
+ const LOG_PREFIX = "[fanvue/auth]";
9
+ //#endregion
10
+ //#region src/nextjs/utils.ts
11
+ /**
12
+ * Fetches the authenticated user's profile from the Fanvue API.
13
+ *
14
+ * @returns The user profile on success, or `null` on failure.
15
+ */
16
+ async function fetchUserProfile(accessToken, apiBaseUrl) {
17
+ const result = await createFanvueClient(accessToken, apiBaseUrl).getCurrentUser();
18
+ if (result.isErr()) {
19
+ console.warn(`${LOG_PREFIX} user profile fetch failed:`, result.error);
20
+ return null;
21
+ }
22
+ return result.value;
23
+ }
24
+ /**
25
+ * Extracts a string value from `FormData`, returning `null` when the value
26
+ * is missing or is not a string (e.g. a `File`).
27
+ */
28
+ function getFormString(form, key) {
29
+ const value = form.get(key);
30
+ return typeof value === "string" ? value : null;
31
+ }
32
+ /**
33
+ * Derives the application base URL from the options or falls back to the
34
+ * request's origin.
35
+ */
36
+ function getBaseUrl(opts, requestUrl) {
37
+ return opts.baseUrl ?? new URL(requestUrl).origin;
38
+ }
39
+ //#endregion
40
+ //#region src/nextjs/index.ts
41
+ /**
42
+ * Resolves configuration from options with environment-variable fallbacks,
43
+ * reporting what is missing instead of throwing.
44
+ *
45
+ * Shared by {@link createConfig} and {@link createConfigSafe} so the two can
46
+ * never drift. The check order — required values first, then the API base-URL
47
+ * domain — is part of `createConfig`'s existing contract.
48
+ */
49
+ function resolveConfig(opts) {
50
+ const clientId = opts?.clientId ?? process.env["OAUTH_CLIENT_ID"];
51
+ const clientSecret = opts?.clientSecret ?? process.env["OAUTH_CLIENT_SECRET"];
52
+ const redirectUri = opts?.redirectUri ?? process.env["OAUTH_REDIRECT_URI"];
53
+ const sessionSecret = opts?.sessionSecret ?? process.env["SESSION_SECRET"];
54
+ const missing = [];
55
+ if (!clientId) missing.push("OAUTH_CLIENT_ID");
56
+ if (!clientSecret) missing.push("OAUTH_CLIENT_SECRET");
57
+ if (!redirectUri) missing.push("OAUTH_REDIRECT_URI");
58
+ if (!sessionSecret) missing.push("SESSION_SECRET");
59
+ if (!clientId || !clientSecret || !redirectUri || !sessionSecret) return {
60
+ ok: false,
61
+ missing,
62
+ invalidApiBaseUrl: null
63
+ };
64
+ const apiBaseUrl = opts?.apiBaseUrl ?? process.env["API_BASE_URL"];
65
+ if (apiBaseUrl != null) try {
66
+ assertFanvueDomain(apiBaseUrl);
67
+ } catch {
68
+ return {
69
+ ok: false,
70
+ missing: ["API_BASE_URL"],
71
+ invalidApiBaseUrl: apiBaseUrl
72
+ };
73
+ }
74
+ const issuerUrl = opts?.issuerUrl ?? process.env["OAUTH_ISSUER_BASE_URL"];
75
+ const scopes = opts?.scopes ?? process.env["OAUTH_SCOPES"];
76
+ const responseMode = opts?.responseMode ?? process.env["OAUTH_RESPONSE_MODE"];
77
+ const prompt = opts?.prompt ?? process.env["OAUTH_PROMPT"];
78
+ const baseUrl = opts?.baseUrl ?? process.env["BASE_URL"];
79
+ return {
80
+ ok: true,
81
+ config: {
82
+ clientId,
83
+ clientSecret,
84
+ redirectUri,
85
+ issuerUrl: issuerUrl ?? null,
86
+ apiBaseUrl: apiBaseUrl ?? null,
87
+ scopes: scopes ?? null,
88
+ responseMode: responseMode ?? null,
89
+ prompt: prompt ?? null,
90
+ baseUrl: baseUrl ?? null,
91
+ sessionSecret,
92
+ sessionCookieName: opts?.sessionCookieName ?? process.env["SESSION_COOKIE_NAME"] ?? "fanvue_session"
93
+ }
94
+ };
95
+ }
96
+ /**
97
+ * Resolves a complete {@link ResolvedConfig} from the supplied options,
98
+ * falling back to environment variables for any values not provided.
99
+ *
100
+ * @param opts - Optional overrides for the OAuth and session configuration.
101
+ * @returns A fully-resolved configuration object.
102
+ * @throws If any required value (`clientId`, `clientSecret`, `redirectUri`,
103
+ * `sessionSecret`) is missing from both `opts` and the environment.
104
+ */
105
+ function createConfig(opts) {
106
+ const result = resolveConfig(opts);
107
+ if (!result.ok) {
108
+ if (result.invalidApiBaseUrl !== null) assertFanvueDomain(result.invalidApiBaseUrl);
109
+ throw new Error("Missing required OAuth configuration. Provide via options or env vars: OAUTH_CLIENT_ID, OAUTH_CLIENT_SECRET, OAUTH_REDIRECT_URI, SESSION_SECRET");
110
+ }
111
+ return result.config;
112
+ }
113
+ /**
114
+ * Non-throwing variant of {@link createConfig}.
115
+ *
116
+ * A route that calls the throwing form on an under-configured deployment turns a
117
+ * missing environment variable into an unhandled 500 with a stack trace, which
118
+ * is why every app grew its own `isFanvueConfigured()` pre-check. This returns
119
+ * the same configuration as a discriminated result instead, so a handler can
120
+ * answer with a deliberate "not configured" response.
121
+ *
122
+ * @param opts - Optional overrides for the OAuth and session configuration.
123
+ * @returns `{ ok: true, config }`, or `{ ok: false, missing }` naming the
124
+ * environment variables that are unset (or, for `API_BASE_URL`, set to a
125
+ * non-Fanvue domain).
126
+ * @example
127
+ * const result = createConfigSafe();
128
+ * if (!result.ok) {
129
+ * console.error('Fanvue OAuth not configured:', result.missing.join(', '));
130
+ * return new Response(null, { status: 503 });
131
+ * }
132
+ * const handler = createLoginHandler({ ...result.config, afterLoginPath: null, afterLogoutPath: null });
133
+ */
134
+ function createConfigSafe(opts) {
135
+ const result = resolveConfig(opts);
136
+ return result.ok ? result : {
137
+ ok: false,
138
+ missing: result.missing
139
+ };
140
+ }
141
+ //#endregion
142
+ export { getFormString as a, SESSION_COOKIE_MAX_AGE_SECONDS as c, getBaseUrl as i, createConfigSafe as n, DEFAULT_SESSION_COOKIE_NAME as o, fetchUserProfile as r, LOG_PREFIX as s, createConfig as t };
143
+
144
+ //# sourceMappingURL=nextjs-CvVhtMdb.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nextjs-CvVhtMdb.js","names":[],"sources":["../src/nextjs/constants.ts","../src/nextjs/utils.ts","../src/nextjs/index.ts"],"sourcesContent":["/** Default name for the session cookie. */\nexport const DEFAULT_SESSION_COOKIE_NAME = 'fanvue_session' as const;\n\n/** Session cookie max-age in seconds (30 days). */\nexport const SESSION_COOKIE_MAX_AGE_SECONDS = 60 * 60 * 24 * 30;\n\n/** SameSite attribute for cookies. */\nexport const COOKIE_SAME_SITE = 'lax' as const;\n\n/** Log prefix for all library log messages. */\nexport const LOG_PREFIX = '[fanvue/auth]' as const;\n","import { createFanvueClient } from '../core/index.js';\n\nimport { LOG_PREFIX } from './constants.js';\n\nimport type { FanvueUser } from '../core/index.js';\n\n/**\n * Fetches the authenticated user's profile from the Fanvue API.\n *\n * @returns The user profile on success, or `null` on failure.\n */\nexport async function fetchUserProfile(\n accessToken: string,\n apiBaseUrl: string | null,\n): Promise<FanvueUser | null> {\n const client = createFanvueClient(accessToken, apiBaseUrl);\n const result = await client.getCurrentUser();\n if (result.isErr()) {\n console.warn(`${LOG_PREFIX} user profile fetch failed:`, result.error);\n return null;\n }\n return result.value;\n}\n\n/**\n * Extracts a string value from `FormData`, returning `null` when the value\n * is missing or is not a string (e.g. a `File`).\n */\nexport function getFormString(form: FormData, key: string): string | null {\n const value = form.get(key);\n return typeof value === 'string' ? value : null;\n}\n\n/**\n * Derives the application base URL from the options or falls back to the\n * request's origin.\n */\nexport function getBaseUrl(opts: { baseUrl: string | null }, requestUrl: string): string {\n return opts.baseUrl ?? new URL(requestUrl).origin;\n}\n","import { assertFanvueDomain } from '../core/index.js';\n\nimport { DEFAULT_SESSION_COOKIE_NAME } from './constants.js';\n\nimport type { OAuthConfig } from '../core/index.js';\n\n/**\n * User-facing configuration options for Fanvue OAuth.\n *\n * All fields are optional — when omitted, values are read from the\n * corresponding environment variables at runtime. This type uses optional\n * properties (`?`) rather than nullable fields because every value has an\n * env-var fallback, so `undefined` genuinely means \"use the default.\"\n */\nexport type FanvueAuthOptions = {\n clientId?: string;\n clientSecret?: string;\n redirectUri?: string;\n issuerUrl?: string;\n apiBaseUrl?: string;\n scopes?: string;\n sessionSecret?: string;\n sessionCookieName?: string;\n responseMode?: string;\n prompt?: string;\n baseUrl?: string;\n};\n\n/**\n * Fully-resolved configuration produced by {@link createConfig}.\n *\n * Extends the core {@link OAuthConfig} with session-specific settings that\n * are guaranteed to be present.\n */\nexport type ResolvedConfig = OAuthConfig & {\n sessionSecret: string;\n sessionCookieName: string;\n baseUrl: string | null;\n};\n\n/**\n * The outcome of a non-throwing configuration resolution.\n *\n * `missing` names the environment variables (never the option keys) that would\n * have to be set for resolution to succeed, so the message can be shown to\n * whoever deploys the app.\n */\nexport type CreateConfigResult =\n | { ok: true; config: ResolvedConfig }\n | { ok: false; missing: string[] };\n\n/**\n * Internal resolution outcome.\n *\n * The failure arm names the offending API base URL separately so\n * {@link createConfig} can raise the exact domain error it always has, while\n * {@link createConfigSafe} reports the same condition as a missing variable.\n */\ntype ResolveOutcome =\n | { ok: true; config: ResolvedConfig }\n | { ok: false; missing: string[]; invalidApiBaseUrl: string | null };\n\n/**\n * Resolves configuration from options with environment-variable fallbacks,\n * reporting what is missing instead of throwing.\n *\n * Shared by {@link createConfig} and {@link createConfigSafe} so the two can\n * never drift. The check order — required values first, then the API base-URL\n * domain — is part of `createConfig`'s existing contract.\n */\nfunction resolveConfig(opts?: FanvueAuthOptions): ResolveOutcome {\n const clientId = opts?.clientId ?? process.env['OAUTH_CLIENT_ID'];\n const clientSecret = opts?.clientSecret ?? process.env['OAUTH_CLIENT_SECRET'];\n const redirectUri = opts?.redirectUri ?? process.env['OAUTH_REDIRECT_URI'];\n const sessionSecret = opts?.sessionSecret ?? process.env['SESSION_SECRET'];\n\n const missing: string[] = [];\n if (!clientId) missing.push('OAUTH_CLIENT_ID');\n if (!clientSecret) missing.push('OAUTH_CLIENT_SECRET');\n if (!redirectUri) missing.push('OAUTH_REDIRECT_URI');\n if (!sessionSecret) missing.push('SESSION_SECRET');\n\n // The truthiness checks are repeated rather than tested through\n // `missing.length` so the compiler narrows the four required values to\n // `string` without an assertion.\n if (!clientId || !clientSecret || !redirectUri || !sessionSecret) {\n return { ok: false, missing, invalidApiBaseUrl: null };\n }\n\n const apiBaseUrl = opts?.apiBaseUrl ?? process.env['API_BASE_URL'];\n if (apiBaseUrl != null) {\n // An off-domain API base URL is unusable rather than merely absent, but it\n // is the same class of deployment mistake and belongs in the same report.\n try {\n assertFanvueDomain(apiBaseUrl);\n } catch {\n return { ok: false, missing: ['API_BASE_URL'], invalidApiBaseUrl: apiBaseUrl };\n }\n }\n\n const issuerUrl = opts?.issuerUrl ?? process.env['OAUTH_ISSUER_BASE_URL'];\n const scopes = opts?.scopes ?? process.env['OAUTH_SCOPES'];\n const responseMode = opts?.responseMode ?? process.env['OAUTH_RESPONSE_MODE'];\n const prompt = opts?.prompt ?? process.env['OAUTH_PROMPT'];\n const baseUrl = opts?.baseUrl ?? process.env['BASE_URL'];\n\n return {\n ok: true,\n config: {\n clientId,\n clientSecret,\n redirectUri,\n issuerUrl: issuerUrl ?? null,\n apiBaseUrl: apiBaseUrl ?? null,\n scopes: scopes ?? null,\n responseMode: responseMode ?? null,\n prompt: prompt ?? null,\n baseUrl: baseUrl ?? null,\n sessionSecret,\n sessionCookieName:\n opts?.sessionCookieName ??\n process.env['SESSION_COOKIE_NAME'] ??\n DEFAULT_SESSION_COOKIE_NAME,\n },\n };\n}\n\n/**\n * Resolves a complete {@link ResolvedConfig} from the supplied options,\n * falling back to environment variables for any values not provided.\n *\n * @param opts - Optional overrides for the OAuth and session configuration.\n * @returns A fully-resolved configuration object.\n * @throws If any required value (`clientId`, `clientSecret`, `redirectUri`,\n * `sessionSecret`) is missing from both `opts` and the environment.\n */\nexport function createConfig(opts?: FanvueAuthOptions): ResolvedConfig {\n const result = resolveConfig(opts);\n if (!result.ok) {\n if (result.invalidApiBaseUrl !== null) {\n // Re-raises the original domain error, message unchanged.\n assertFanvueDomain(result.invalidApiBaseUrl);\n }\n throw new Error(\n 'Missing required OAuth configuration. Provide via options or env vars: OAUTH_CLIENT_ID, OAUTH_CLIENT_SECRET, OAUTH_REDIRECT_URI, SESSION_SECRET',\n );\n }\n return result.config;\n}\n\n/**\n * Non-throwing variant of {@link createConfig}.\n *\n * A route that calls the throwing form on an under-configured deployment turns a\n * missing environment variable into an unhandled 500 with a stack trace, which\n * is why every app grew its own `isFanvueConfigured()` pre-check. This returns\n * the same configuration as a discriminated result instead, so a handler can\n * answer with a deliberate \"not configured\" response.\n *\n * @param opts - Optional overrides for the OAuth and session configuration.\n * @returns `{ ok: true, config }`, or `{ ok: false, missing }` naming the\n * environment variables that are unset (or, for `API_BASE_URL`, set to a\n * non-Fanvue domain).\n * @example\n * const result = createConfigSafe();\n * if (!result.ok) {\n * console.error('Fanvue OAuth not configured:', result.missing.join(', '));\n * return new Response(null, { status: 503 });\n * }\n * const handler = createLoginHandler({ ...result.config, afterLoginPath: null, afterLogoutPath: null });\n */\nexport function createConfigSafe(opts?: FanvueAuthOptions): CreateConfigResult {\n const result = resolveConfig(opts);\n return result.ok ? result : { ok: false, missing: result.missing };\n}\n"],"mappings":";;;AACA,MAAa,8BAA8B;;AAG3C,MAAa,iCAAiC,OAAU,KAAK;;AAM7D,MAAa,aAAa;;;;;;;;ACC1B,eAAsB,iBACpB,aACA,YAC4B;CAE5B,MAAM,SAAS,MADA,mBAAmB,aAAa,WAAW,CAC9B,gBAAgB;AAC5C,KAAI,OAAO,OAAO,EAAE;AAClB,UAAQ,KAAK,GAAG,WAAW,8BAA8B,OAAO,MAAM;AACtE,SAAO;;AAET,QAAO,OAAO;;;;;;AAOhB,SAAgB,cAAc,MAAgB,KAA4B;CACxE,MAAM,QAAQ,KAAK,IAAI,IAAI;AAC3B,QAAO,OAAO,UAAU,WAAW,QAAQ;;;;;;AAO7C,SAAgB,WAAW,MAAkC,YAA4B;AACvF,QAAO,KAAK,WAAW,IAAI,IAAI,WAAW,CAAC;;;;;;;;;;;;ACgC7C,SAAS,cAAc,MAA0C;CAC/D,MAAM,WAAW,MAAM,YAAY,QAAQ,IAAI;CAC/C,MAAM,eAAe,MAAM,gBAAgB,QAAQ,IAAI;CACvD,MAAM,cAAc,MAAM,eAAe,QAAQ,IAAI;CACrD,MAAM,gBAAgB,MAAM,iBAAiB,QAAQ,IAAI;CAEzD,MAAM,UAAoB,EAAE;AAC5B,KAAI,CAAC,SAAU,SAAQ,KAAK,kBAAkB;AAC9C,KAAI,CAAC,aAAc,SAAQ,KAAK,sBAAsB;AACtD,KAAI,CAAC,YAAa,SAAQ,KAAK,qBAAqB;AACpD,KAAI,CAAC,cAAe,SAAQ,KAAK,iBAAiB;AAKlD,KAAI,CAAC,YAAY,CAAC,gBAAgB,CAAC,eAAe,CAAC,cACjD,QAAO;EAAE,IAAI;EAAO;EAAS,mBAAmB;EAAM;CAGxD,MAAM,aAAa,MAAM,cAAc,QAAQ,IAAI;AACnD,KAAI,cAAc,KAGhB,KAAI;AACF,qBAAmB,WAAW;SACxB;AACN,SAAO;GAAE,IAAI;GAAO,SAAS,CAAC,eAAe;GAAE,mBAAmB;GAAY;;CAIlF,MAAM,YAAY,MAAM,aAAa,QAAQ,IAAI;CACjD,MAAM,SAAS,MAAM,UAAU,QAAQ,IAAI;CAC3C,MAAM,eAAe,MAAM,gBAAgB,QAAQ,IAAI;CACvD,MAAM,SAAS,MAAM,UAAU,QAAQ,IAAI;CAC3C,MAAM,UAAU,MAAM,WAAW,QAAQ,IAAI;AAE7C,QAAO;EACL,IAAI;EACJ,QAAQ;GACN;GACA;GACA;GACA,WAAW,aAAa;GACxB,YAAY,cAAc;GAC1B,QAAQ,UAAU;GAClB,cAAc,gBAAgB;GAC9B,QAAQ,UAAU;GAClB,SAAS,WAAW;GACpB;GACA,mBACE,MAAM,qBACN,QAAQ,IAAI,0BAAA;GAEf;EACF;;;;;;;;;;;AAYH,SAAgB,aAAa,MAA0C;CACrE,MAAM,SAAS,cAAc,KAAK;AAClC,KAAI,CAAC,OAAO,IAAI;AACd,MAAI,OAAO,sBAAsB,KAE/B,oBAAmB,OAAO,kBAAkB;AAE9C,QAAM,IAAI,MACR,kJACD;;AAEH,QAAO,OAAO;;;;;;;;;;;;;;;;;;;;;;;AAwBhB,SAAgB,iBAAiB,MAA8C;CAC7E,MAAM,SAAS,cAAc,KAAK;AAClC,QAAO,OAAO,KAAK,SAAS;EAAE,IAAI;EAAO,SAAS,OAAO;EAAS"}
@@ -1,5 +1,5 @@
1
- import { l as AnalyticsProperties } from "../index-BRDYLBlc.js";
2
- import { c as FanvueTheme } from "../index-ClbZoV_Z.js";
1
+ import { l as AnalyticsProperties } from "../index-CEFYZtlb.js";
2
+ import { j as FanvueTheme } from "../index-C3CXrRiw.js";
3
3
  import { ReactNode } from "react";
4
4
 
5
5
  //#region src/react/auth-context.d.ts
@@ -1,7 +1,7 @@
1
1
  "use client";
2
- import "../schemas-DbyHF7Xi.js";
3
- import { C as HEADER_UPDATED_SESSION, S as BEARER_PREFIX, i as getThemeFromUrl, r as getSessionTokenFromUrl } from "../core-BqdYUMrJ.js";
4
- import { t as connectFanvueBridge } from "../bridge-CGVtI3hr.js";
2
+ import "../index.cjs-teGsk6HB.js";
3
+ import { bt as BEARER_PREFIX, h as getThemeFromUrl, m as getSessionTokenFromUrl, xt as HEADER_UPDATED_SESSION } from "../core-BhKiA55a.js";
4
+ import { t as connectFanvueBridge } from "../bridge-QnF7P4Co.js";
5
5
  import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
6
6
  import { jsx } from "react/jsx-runtime";
7
7
  //#region src/react/auth-context.tsx
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fanvue/builder-sdk",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "packageManager": "pnpm@10.30.1",
5
5
  "description": "OAuth authentication library for the Fanvue API",
6
6
  "type": "module",