diff --git a/src/.eslintrc.base.json b/.eslintrc.base.json similarity index 100% rename from src/.eslintrc.base.json rename to .eslintrc.base.json diff --git a/src/.eslintrc.cjs b/.eslintrc.cjs similarity index 100% rename from src/.eslintrc.cjs rename to .eslintrc.cjs diff --git a/Makefile b/Makefile index c9ce47f8a..411a98618 100644 --- a/Makefile +++ b/Makefile @@ -10,12 +10,17 @@ install: test: npm run test +build: + npm run build + +dev: + npm run build + test-containerized: # https://github.com/microsoft/playwright/issues/26482 # For unsupported distros, use the `test-containerized` target instead of `test` sh -c ./playwright-docker.sh - PRETTIER_VERSION=$(shell cat package.json | jq -r '.devDependencies["prettier"] // .dependencies["prettier"]') format: .bin/ory @@ -23,7 +28,6 @@ format: .bin/ory @echo "Prettier Version: $(PRETTIER_VERSION)" npx prettier@$$PRETTIER_VERSION --write . - licenses: .bin/licenses node_modules # checks open-source licenses .bin/licenses diff --git a/examples/nextjs-pages-router/pages/index.tsx b/examples/nextjs-pages-router/pages/index.tsx index de8ba21e5..675b72bf5 100644 --- a/examples/nextjs-pages-router/pages/index.tsx +++ b/examples/nextjs-pages-router/pages/index.tsx @@ -1,9 +1,9 @@ // Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 -import React from "react"; -import Link from "next/link"; -import {useSession} from "@ory/nextjs/hooks"; +import React from "react" +import Link from "next/link" +import { useSession } from "@ory/nextjs/hooks" export default function Home() { const session = useSession() @@ -11,8 +11,8 @@ export default function Home() { return "Hello: " + session.identity?.traits.email } return ( -

- Not authenticated, please log in. -

+

+ Not authenticated, please log in. +

) } diff --git a/packages/elements-react/src/client/config.ts b/packages/elements-react/src/client/config.ts new file mode 100644 index 000000000..453a624a5 --- /dev/null +++ b/packages/elements-react/src/client/config.ts @@ -0,0 +1,87 @@ +// Copyright © 2024 Ory Corp +// SPDX-License-Identifier: Apache-2.0 + +/** + * This function returns the base URL of the Ory SDK as set by environment variables `NEXT_PUBLIC_ORY_SDK_URL` or `ORY_SDK_URL`. + */ +export function orySdkUrl() { + let baseUrl + + if (process.env["NEXT_PUBLIC_ORY_SDK_URL"]) { + baseUrl = process.env["NEXT_PUBLIC_ORY_SDK_URL"] + } + + if (process.env["ORY_SDK_URL"]) { + baseUrl = process.env["_ORY_SDK_URL"] + } + + if (!baseUrl) { + throw new Error( + "You need to set environment variable `NEXT_PUBLIC_ORY_SDK_URL` or if you don't use Next.js `ORY_SDK_URL` to your Ory Network SDK URL.", + ) + } + + return baseUrl.replace(/\/$/, "") +} + +/** + * This function returns whether the current environment is a production environment. + */ +export function isProduction() { + return ( + ["production", "prod"].indexOf( + process.env["VERCEL_ENV"] || process.env["NODE_ENV"] || "", + ) > -1 + ) +} + +/** + * This function returns the Ory SDK URL. If the environment is not production, it tries to guess the SDK URL based on the environment variables, assuming + * that Ory APIs are proxied through the same domain as the application. + * + * Currently, this is only tested for Vercel deployments. + * + * @param options + */ +export function guessPotentiallyProxiedOrySdkUrl(options?: { + knownProxiedUrl?: string +}) { + if (isProduction()) { + // In production, we use the production custom domain + return orySdkUrl() + } + + if (process.env["VERCEL_ENV"]) { + // We are in vercel + + // The domain name of the generated deployment URL. Example: *.vercel.app. The value does not include the protocol scheme https://. + // + // This is only available for preview deployments on Vercel. + if (!isProduction() && process.env["VERCEL_URL"]) { + return `https://${process.env["VERCEL_URL"]}`.replace(/\/$/, "") + } + + // This is sometimes set by the render server. + if (process.env["__NEXT_PRIVATE_ORIGIN"]) { + return process.env["__NEXT_PRIVATE_ORIGIN"].replace(/\/$/, "") + } + } + + // Unable to figure out the SDK URL. Either because we are not using Vercel or because we are on a local machine. + // Let's try to use the window location. + if (typeof window !== "undefined") { + return window.location.origin + } + + if (options?.knownProxiedUrl) { + return options.knownProxiedUrl + } + + // We tried everything. Let's use the SDK URL. + const final = orySdkUrl() + console.warn( + `Unable to determine a suitable SDK URL for setting up the Next.js integration of Ory Elements. Will proceed using default Ory SDK URL "${final}". This is likely not what you want for local development and your authentication and login may not work.`, + ) + + return final +} diff --git a/packages/elements-react/src/client/frontendClient.ts b/packages/elements-react/src/client/frontendClient.ts index 66cd0bb2f..d540e3767 100644 --- a/packages/elements-react/src/client/frontendClient.ts +++ b/packages/elements-react/src/client/frontendClient.ts @@ -1,19 +1,31 @@ // Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 - +"use client" import { Configuration, ConfigurationParameters, FrontendApi, } from "@ory/client-fetch" +import { guessPotentiallyProxiedOrySdkUrl } from "./config" export function frontendClient( - sdkUrl: string, - opts: Partial = {}, + { + forceBaseUrl, + ...opts + }: Partial = { + credentials: "include", + }, ) { + const basePath = + forceBaseUrl ?? + guessPotentiallyProxiedOrySdkUrl({ + knownProxiedUrl: window.location.origin, + }) + const config = new Configuration({ ...opts, - basePath: sdkUrl, + basePath: basePath?.replace(/\/$/, ""), + credentials: opts.credentials || "include", headers: { Accept: "application/json", ...opts.headers, diff --git a/packages/nextjs/src/hooks/useSession.spec.tsx b/packages/elements-react/src/client/useSession.spec.tsx similarity index 86% rename from packages/nextjs/src/hooks/useSession.spec.tsx rename to packages/elements-react/src/client/useSession.spec.tsx index d2cefaa4c..78caf9265 100644 --- a/packages/nextjs/src/hooks/useSession.spec.tsx +++ b/packages/elements-react/src/client/useSession.spec.tsx @@ -8,10 +8,10 @@ import "@testing-library/jest-dom" import "@testing-library/jest-dom/jest-globals" import { act, render, screen, waitFor } from "@testing-library/react" import { sessionStore, useSession } from "./useSession" -import {newOryFrontendClient} from "../utils/sdk"; +import { frontendClient } from "./frontendClient" -jest.mock("./newOryFrontendClient", () => ({ - newOrynewOryFrontendClient: jest.fn(() => ({ +jest.mock("./frontendClient", () => ({ + frontendClient: jest.fn(() => ({ toSession: jest.fn(), })), })) @@ -49,7 +49,7 @@ describe("useSession", () => { }) it("fetches and sets session successfully", async () => { - ;(newOryFrontendClient as jest.Mock).mockReturnValue({ + ;(frontendClient as jest.Mock).mockReturnValue({ toSession: jest.fn().mockResolvedValue(mockSession), }) @@ -70,7 +70,7 @@ describe("useSession", () => { }) it("doesn't refetch session if a session is set", async () => { - ;(newOryFrontendClient as jest.Mock).mockReturnValue({ + ;(frontendClient as jest.Mock).mockReturnValue({ toSession: jest.fn().mockResolvedValue(mockSession), }) @@ -91,7 +91,7 @@ describe("useSession", () => { // this is fine, because jest is not calling the function // eslint-disable-next-line @typescript-eslint/unbound-method - expect(newOryFrontendClient("").toSession).toHaveBeenCalledTimes(1) + expect(frontendClient().toSession).toHaveBeenCalledTimes(1) act(() => { render() @@ -99,12 +99,12 @@ describe("useSession", () => { // this is fine, because jest is not calling the function // eslint-disable-next-line @typescript-eslint/unbound-method - expect(newOryFrontendClient("").toSession).toHaveBeenCalledTimes(1) + expect(frontendClient().toSession).toHaveBeenCalledTimes(1) }) it("handles errors during session fetching", async () => { const errorMessage = "Failed to fetch session" - ;(newOryFrontendClient as jest.Mock).mockReturnValue({ + ;(frontendClient as jest.Mock).mockReturnValue({ toSession: jest.fn().mockRejectedValue(new Error(errorMessage)), }) @@ -123,7 +123,7 @@ describe("useSession", () => { }) it("does not fetch session if already loading or session is set", async () => { - ;(newOryFrontendClient as jest.Mock).mockReturnValue({ + ;(frontendClient as jest.Mock).mockReturnValue({ toSession: jest.fn(), }) @@ -140,6 +140,6 @@ describe("useSession", () => { // this is fine, because jest is not calling the function // eslint-disable-next-line @typescript-eslint/unbound-method - expect(newOryFrontendClient("").toSession).toHaveBeenCalledTimes(1) + expect(frontendClient().toSession).toHaveBeenCalledTimes(1) }) }) diff --git a/packages/nextjs/src/hooks/useSession.ts b/packages/elements-react/src/client/useSession.ts similarity index 79% rename from packages/nextjs/src/hooks/useSession.ts rename to packages/elements-react/src/client/useSession.ts index 0d7684033..d65b7de19 100644 --- a/packages/nextjs/src/hooks/useSession.ts +++ b/packages/elements-react/src/client/useSession.ts @@ -6,7 +6,7 @@ import { Session } from "@ory/client-fetch" import { useCallback, useEffect } from "react" import { create, useStore } from "zustand" import { subscribeWithSelector } from "zustand/middleware" -import {guessPotentiallyProxiedOrySdkUrl, newOryFrontendClient} from "../utils/sdk"; +import { frontendClient } from "./frontendClient" type SessionStore = { setIsLoading: (loading: boolean) => void @@ -38,10 +38,13 @@ export const sessionStore = create()( * * @returns The current session, error and loading state. */ -export const useSession = (config?: { sdk: { url: string } }) => { +export const useSession = (config: { orySdkUrl?: string } = {}) => { const store = useStore(sessionStore) const fetchSession = useCallback(async () => { + const client = frontendClient({ + forceBaseUrl: config.orySdkUrl, + }) const { session, isLoading, setSession, setIsLoading, setError } = sessionStore.getState() @@ -52,21 +55,19 @@ export const useSession = (config?: { sdk: { url: string } }) => { setIsLoading(true) try { - const sessionData = await newOryFrontendClient(guessPotentiallyProxiedOrySdkUrl({ - knownProxiedUrl: window.location.origin - })).toSession() + const sessionData = await client.toSession() setSession(sessionData) } catch (e) { setError(e instanceof Error ? e.message : "Unknown error occurred") - if (!config?.sdk.url) { + if (!config?.orySdkUrl) { console.error( - "Could not fetch session. Make sure you have set the SDK URL in the config.", + "Could not fetch session. Make sure you have set your environment variables correctly.", ) } } finally { setIsLoading(false) } - }, [config?.sdk.url]) + }, [config?.orySdkUrl]) useEffect(() => { void fetchSession() diff --git a/packages/elements-react/src/theme/default/components/generic/page-header.tsx b/packages/elements-react/src/theme/default/components/generic/page-header.tsx index 1533cced1..b4d2e9d87 100644 --- a/packages/elements-react/src/theme/default/components/generic/page-header.tsx +++ b/packages/elements-react/src/theme/default/components/generic/page-header.tsx @@ -3,7 +3,7 @@ import { OryPageHeaderProps, useComponents } from "@ory/elements-react" import { UserMenu } from "../ui/user-menu" -import { useSession } from "@ory/elements-react/client" +import { useSession } from "../../../../client" export const DefaultPageHeader = (_props: OryPageHeaderProps) => { const { Card } = useComponents() diff --git a/packages/nextjs/src/hooks/index.ts b/packages/nextjs/src/hooks/index.ts index f7267b954..5e2c58350 100644 --- a/packages/nextjs/src/hooks/index.ts +++ b/packages/nextjs/src/hooks/index.ts @@ -1,4 +1,3 @@ // Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 "use client" -export { useSession } from "./useSession" diff --git a/packages/nextjs/src/pages/client.ts b/packages/nextjs/src/pages/client.ts index ee6b68704..7fb58252d 100644 --- a/packages/nextjs/src/pages/client.ts +++ b/packages/nextjs/src/pages/client.ts @@ -9,6 +9,7 @@ export const clientSideFrontendClient = new FrontendApi( headers: { Accept: "application/json", }, + credentials: "include", basePath: guessPotentiallyProxiedOrySdkUrl(), }), )