diff --git a/.gitignore b/.gitignore index 930c9799..f4f9fa2d 100644 --- a/.gitignore +++ b/.gitignore @@ -108,3 +108,6 @@ test-results cypress/screenshots cypress/videos + +# Docs artefact produced by `npm run docs:docsv2` (see typedoc.docsv2.js `json`) +mintlify/docsv2/sdk-artifacts/ diff --git a/package.json b/package.json index ad00e544..3f5f4af5 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,8 @@ "test:dist": "npm run build && npm run test:dist:only", "test:dist:only": "jest use-client-directive", "prepack": "npm run build", - "docs": "typedoc --options typedoc.js src", + "docs": "typedoc --options ./typedoc.js", + "docs:docsv2": "typedoc --options ./typedoc.docsv2.js", "install:examples": "npm i --prefix=examples/vite-react-router --no-package-lock --legacy-peer-deps && npm i --prefix=examples/gatsby-app --no-package-lock --legacy-peer-deps && npm i --prefix=examples/nextjs-app --no-package-lock --legacy-peer-deps && npm ci --prefix=examples/users-api", "start:vite": "npm start --prefix=examples/vite-react-router", "start:gatsby": "npm start --prefix=examples/gatsby-app", diff --git a/scripts/typedoc-plugin.js b/scripts/typedoc-plugin.js new file mode 100644 index 00000000..2ecfaa60 --- /dev/null +++ b/scripts/typedoc-plugin.js @@ -0,0 +1,380 @@ +// @ts-check +/** + * TypeDoc plugin that shapes the generated API reference for readability. + * + * Two jobs: + * + * 1. Categorize every top-level export into named sections instead of one flat + * alphabetical list. Own symbols carry an `@category` tag; re-exports are + * placed by rule. An own symbol that no rule places fails the build. + * + * 2. Put the context interface's members directly in the sidebar, so a token or + * login method is one click away. The default theme stops the nav tree at + * module level, so we extend DefaultTheme to add them. + */ +const { + Comment, + CommentTag, + Converter, + DefaultTheme, + JSX, + ReflectionKind, +} = require('typedoc'); + +/** + * Interfaces that are the SDK's real entry points: everything a component gets + * back from `useAuth0()`. Their members go in the sidebar. + */ +const ENTRY_INTERFACES = ['Auth0ContextInterface']; + +/** + * A re-export is told apart from an own symbol by whether its source resolves + * inside `node_modules`. Keyed on this rather than a `src/` prefix: the prefix + * is relative to TypeDoc's basePath, so a layout change would silently + * reclassify own symbols as `Reference` and skip validation. `ownSymbolCount` + * below is the backstop if the discriminator ever matches nothing. + */ +const DEPENDENCY_SOURCE_MARKER = 'node_modules'; + +/** The practical fix named in diagnostics: put an `@category` in `src/`. */ +const OWN_SOURCE_DIR = 'src/'; + +const SETUP = 'Getting Started'; +const HOOKS = 'Hooks & HOCs'; +const CONTEXT = 'Context'; +const ERRORS = 'Errors'; +const REFERENCE = 'Reference'; + +/** Fallback if a symbol escapes every rule; validation makes it unreachable. */ +const DEFAULT_CATEGORY = 'Other Types'; + +/** + * Section order, following how the reference is read: provider, hooks, context, + * errors. `Reference` is last (reached from a signature, not browsed); `*` is + * where any unlisted category lands. + */ +const CATEGORY_ORDER = [ + SETUP, + HOOKS, + CONTEXT, + ERRORS, + REFERENCE, + '*', + DEFAULT_CATEGORY, +]; + +/** Allowed categories for a top-level export; anything else is a typo. */ +const TOP_LEVEL_CATEGORIES = [SETUP, HOOKS, CONTEXT, ERRORS, REFERENCE]; + +/** + * Member category order and allowed set for `Auth0ContextInterface`. Kept + * separate from the top-level set so a mix-up between the two is caught. + */ +const MEMBER_CATEGORY_ORDER = [ + 'Auth State', + 'Sub-clients', + 'Authentication', + 'Tokens', + 'User Profile', + 'Connected Accounts', + 'Advanced', +]; + +/** + * Context member name -> sidebar position. Captured while the `@category` tags + * exist, because the renderer runs after TypeDoc has stripped them. + */ +const memberSidebarOrder = new Map(); + +/** + * @param {import('typedoc').DeclarationReflection} reflection + * @returns {string} + */ +function sourceFile(reflection) { + return reflection.sources?.[0]?.fileName ?? ''; +} + +/** @param {import('typedoc').DeclarationReflection} reflection */ +function declaredHere(reflection) { + return !sourceFile(reflection).includes(DEPENDENCY_SOURCE_MARKER); +} + +/** + * The comment carrying the tags. For an arrow-function export the doc block + * attaches to the signature rather than the declaration. + * + * @param {import('typedoc').DeclarationReflection} reflection + */ +function commentOf(reflection) { + return reflection.comment ?? reflection.signatures?.[0]?.comment; +} + +/** + * The category written in the source, if any. + * + * @param {import('typedoc').DeclarationReflection} reflection + * @returns {string | undefined} + */ +function writtenCategory(reflection) { + const tag = commentOf(reflection)?.getTag('@category'); + return tag ? Comment.combineDisplayParts(tag.content).trim() : undefined; +} + +/** + * Stamp an `@category` tag on a reflection, replacing whatever is already + * there. + * + * @param {import('typedoc').DeclarationReflection} reflection + * @param {string} category + */ +function setCategory(reflection, category) { + const comment = commentOf(reflection); + const tag = new CommentTag('@category', [{ kind: 'text', text: category }]); + + if (comment) { + comment.removeTags('@category'); + comment.blockTags.push(tag); + } else { + // Undocumented symbol: give it a comment so it can still be grouped. + reflection.comment = new Comment([], [tag]); + } +} + +/** + * Decide a symbol's category. Three rules, first match wins: + * + * 1. A tag on an own symbol (source outside node_modules): kept and validated. + * 2. A name ending in `Error`: an error class whoever declared it. Precedes + * rule 3 because error classes are re-exports too, and tests the name + * because upstream tags only some of its error modules. Note this catches an + * own `FooError` left untagged before the rule-3 build error would. + * 3. A re-export: a supporting type reached from a signature, so `Reference`. + * Overwrites any inherited category (TypeDoc reads `@category` out of a + * dependency's .d.ts), which would otherwise strand it in an unlisted + * section. + * + * An own symbol that no rule places is the drift this guards against: build + * error. + * + * @param {import('typedoc').DeclarationReflection} reflection + * @param {string[]} allowed Categories this reflection may be tagged with. + * @param {string[]} problems Collects anything that should fail the build. + * @returns {string} the category the symbol ended up in + */ +function categorize(reflection, allowed, problems) { + const here = declaredHere(reflection); + const written = here ? writtenCategory(reflection) : undefined; + + if (written) { + if (!allowed.includes(written)) { + problems.push( + `${reflection.name} (${sourceFile(reflection)}) is tagged ` + + `"@category ${written}", which is not a category this SDK defines ` + + `here. Expected one of: ${allowed.join(', ')}.` + ); + } + return written; + } + + if (/Error$/.test(reflection.name)) { + setCategory(reflection, ERRORS); + return ERRORS; + } + + if (!here) { + setCategory(reflection, REFERENCE); + return REFERENCE; + } + + problems.push( + `${reflection.name} (${sourceFile(reflection)}) has no @category tag. ` + + `Every symbol declared in ${OWN_SOURCE_DIR} needs one: ` + + `${allowed.join(', ')}.` + ); + return DEFAULT_CATEGORY; +} + +/** @param {import('typedoc').DeclarationReflection} reflection */ +function memberCategoryIndex(reflection) { + const written = writtenCategory(reflection) ?? ''; + const index = MEMBER_CATEGORY_ORDER.indexOf(written); + return index === -1 ? MEMBER_CATEGORY_ORDER.length : index; +} + +/** + * Sidebar order for context members: by category, then declaration line within + * it (alphabetical would bury the common ones under the DPoP escape hatches). + * Line-based ordering holds only while a category lives in one file, true today. + * + * @param {import('typedoc').DeclarationReflection} a + * @param {import('typedoc').DeclarationReflection} b + */ +function compareMembers(a, b) { + const byCategory = memberCategoryIndex(a) - memberCategoryIndex(b); + if (byCategory !== 0) return byCategory; + return (a.sources?.[0]?.line ?? 0) - (b.sources?.[0]?.line ?? 0); +} + +/** @param {import('typedoc').Application} app */ +function load(app) { + // Priority 1000 so this runs before the built-in CategoryPlugin, which also + // listens on RESOLVE_END and reads (then strips) `@category` tags. + app.converter.on( + Converter.EVENT_RESOLVE_END, + (context) => { + const { project } = context; + /** @type {string[]} */ + const problems = []; + let referenceCount = 0; + let reExportCount = 0; + let ownSymbolCount = 0; + + for (const child of project.children ?? []) { + if (declaredHere(child)) ownSymbolCount++; + if (categorize(child, TOP_LEVEL_CATEGORIES, problems) === REFERENCE) { + referenceCount++; + if (!declaredHere(child)) reExportCount++; + } + } + + // Backstop for `declaredHere`: this SDK always has own exports, so zero + // means the discriminator broke and everything slipped into `Reference` + // unchecked. Fail rather than ship a reference with no sections. + if ((project.children?.length ?? 0) > 0 && ownSymbolCount === 0) { + problems.push( + 'No top-level export was recognised as declared in this SDK. The ' + + 'test for own symbols (source path outside ' + + `"${DEPENDENCY_SOURCE_MARKER}") is matching nothing, so category ` + + 'validation never ran. This is a plugin bug, not a docs error.' + ); + } + + for (const name of ENTRY_INTERFACES) { + const entry = project.getChildByName(name); + const members = [...(entry?.children ?? [])]; + + for (const member of members) { + categorize(member, MEMBER_CATEGORY_ORDER, problems); + } + + members.sort(compareMembers); + members.forEach((member, index) => + memberSidebarOrder.set(member.name, index) + ); + } + + for (const problem of problems) { + app.logger.error(problem); + } + + // One summary line: a jump here means a dependency added exports. + app.logger.info( + `${REFERENCE} holds ${referenceCount} symbols, ` + + `${reExportCount} of them re-exported from dependencies.` + ); + }, + undefined, + 1000 + ); + + app.renderer.defineTheme( + 'auth0', + class extends DefaultTheme { + buildNavigation(project) { + const navigation = super.buildNavigation(project); + addEntryMembers(navigation, project, this.router); + return navigation; + } + } + ); + + // Groups start collapsed client-side, so seed the two people arrive for as + // expanded. Only seed when unset, so a reader's own collapse sticks. The + // accordion key is `data-key` (ancestor titles joined by `$`); the + // lowercase-dashed form is TypeDoc's fallback, seeded too. + const expandKeys = [SETUP, HOOKS].flatMap((title) => [ + title, + title.replace(/\s+/g, '-').toLowerCase(), + ]); + + app.renderer.hooks.on('body.begin', () => + JSX.createElement( + 'script', + null, + JSX.createElement(JSX.Raw, { + html: `try{${JSON.stringify( + expandKeys + )}.forEach(function(t){var k='tsd-accordion-'+t;if(localStorage.getItem(k)===null)localStorage.setItem(k,'true')})}catch(e){}`, + }) + ) + ); +} + +/** + * Walk the navigation tree and hang each entry interface's members off its node. + * + * @param {any[]} nodes + * @param {import('typedoc').ProjectReflection} project + * @param {import('typedoc').Router} router + */ +function addEntryMembers(nodes, project, router) { + for (const node of nodes) { + // Recurse into groups; entry-interface nodes are leaves under the current + // nav config, so member injection below only fires on leaves. + if (node.children?.length) { + addEntryMembers(node.children, project, router); + continue; + } + + if (!ENTRY_INTERFACES.includes(node.text)) continue; + + const owner = project.getChildByName(node.text); + if (!owner?.children) continue; + + const members = owner.children.filter( + (member) => + member.kindOf( + ReflectionKind.Method | + ReflectionKind.Accessor | + ReflectionKind.Property + ) && + !member.flags.isPrivate && + !member.flags.isProtected && + member.name !== 'constructor' + ); + + members.sort( + (a, b) => + (memberSidebarOrder.get(a.name) ?? Number.MAX_SAFE_INTEGER) - + (memberSidebarOrder.get(b.name) ?? Number.MAX_SAFE_INTEGER) + ); + + // TypeDoc 0.28 moved URLs behind the Router, so `member.url`/`.anchor` are + // undefined here. `getFullUrl` (what the frontend uses for nav) includes + // the anchor. + const links = members + .filter((member) => router.hasUrl(member)) + .map((member) => ({ + text: member.name, + path: router.getFullUrl(member), + kind: member.kind, + class: member.isDeprecated() ? 'deprecated' : undefined, + })); + + if (links.length) { + node.children = links; + } + } +} + +/** + * `categoryOrder` is one global setting covering both top-level and member + * categories. The sets are disjoint, so concatenating orders each page right. + */ +const ALL_CATEGORY_ORDER = [...MEMBER_CATEGORY_ORDER, ...CATEGORY_ORDER]; + +module.exports = { + load, + CATEGORY_ORDER: ALL_CATEGORY_ORDER, + DEFAULT_CATEGORY, +}; diff --git a/src/auth-state.tsx b/src/auth-state.tsx index 70610f23..63f43040 100644 --- a/src/auth-state.tsx +++ b/src/auth-state.tsx @@ -4,10 +4,35 @@ import { User } from '@auth0/auth0-spa-js'; * The auth state which, when combined with the auth methods, make up the return object of the `useAuth0` hook. */ export interface AuthState { - error: Error | undefined; - isAuthenticated: boolean; + /** + * `true` while the SDK is initialising or a redirect callback is being + * handled. Render a loading state rather than trusting `isAuthenticated` + * until this is `false`. + * + * @category Auth State + */ isLoading: boolean; + + /** + * `true` once there is a valid session for the user. + * + * @category Auth State + */ + isAuthenticated: boolean; + + /** + * The authenticated user's profile, or `undefined` when there is no session. + * + * @category Auth State + */ user: TUser | undefined; + + /** + * The error from the last failed authentication attempt, if any. + * + * @category Auth State + */ + error: Error | undefined; } /** diff --git a/src/auth0-context.tsx b/src/auth0-context.tsx index fa040e4d..415179a6 100644 --- a/src/auth0-context.tsx +++ b/src/auth0-context.tsx @@ -22,14 +22,33 @@ import { createContext } from 'react'; import { AuthState, initialAuthState } from './auth-state'; import { AppState } from './auth0-provider'; -// eslint-disable-next-line @typescript-eslint/no-empty-object-type +/* eslint-disable @typescript-eslint/no-empty-object-type -- both narrow an + upstream type by dropping the SDK-managed `onRedirect`, so they add no members + of their own. A disable block rather than per-line comments: an interleaved + line comment between the TSDoc and the declaration hides the `@category` tag + from TypeDoc. */ + +/** + * Options for {@link Auth0ContextInterface.logout}. + * + * @category Reference + */ export interface LogoutOptions extends Omit {} -// eslint-disable-next-line @typescript-eslint/no-empty-object-type + +/** + * Options for {@link Auth0ContextInterface.loginWithRedirect}. + * + * @category Reference + */ export interface RedirectLoginOptions extends Omit, 'onRedirect'> {} +/* eslint-enable @typescript-eslint/no-empty-object-type */ + /** * Contains the authenticated state and authentication methods provided by the `useAuth0` hook. + * + * @category Context */ export interface Auth0ContextInterface extends AuthState { @@ -58,6 +77,8 @@ export interface Auth0ContextInterface * * Note that in all cases, falling back to an iframe requires access to * the `auth0` cookie. + * + * @category Tokens */ getAccessTokenSilently: Auth0Client['getTokenSilently']; @@ -72,6 +93,8 @@ export interface Auth0ContextInterface * provided as arguments. Random and secure `state` and `nonce` * parameters will be auto-generated. If the response is successful, * results will be valid according to their expiration times. + * + * @category Tokens */ getAccessTokenWithPopup: ( options?: GetTokenWithPopupOptions, @@ -84,6 +107,8 @@ export interface Auth0ContextInterface * ``` * * Returns all claims from the id_token if available. + * + * @category User Profile */ getIdTokenClaims: () => Promise; @@ -134,6 +159,8 @@ export interface Auth0ContextInterface * console.error('Token exchange failed:', error); * } * ``` + * + * @category Tokens */ loginWithCustomTokenExchange: ( options: CustomTokenExchangeOptions @@ -161,6 +188,8 @@ export interface Auth0ContextInterface * * @param options - The options required to perform the token exchange. * @returns A promise that resolves to the token endpoint response. + * + * @category Tokens */ customTokenExchange: ( options: CustomTokenExchangeOptions @@ -194,6 +223,8 @@ export interface Auth0ContextInterface * * @param options - The options required to perform the token exchange * @returns A promise that resolves to the token endpoint response containing Auth0 tokens + * + * @category Tokens */ exchangeToken: ( options: CustomTokenExchangeOptions @@ -207,6 +238,8 @@ export interface Auth0ContextInterface * Performs a redirect to `/authorize` using the parameters * provided as arguments. Random and secure `state` and `nonce` * parameters will be auto-generated. + * + * @category Authentication */ loginWithRedirect: ( options?: RedirectLoginOptions @@ -225,6 +258,8 @@ export interface Auth0ContextInterface * IMPORTANT: This method has to be called from an event handler * that was started by the user like a button click, for example, * otherwise the popup will be blocked in most browsers. + * + * @category Authentication */ loginWithPopup: ( options?: PopupLoginOptions, @@ -248,6 +283,8 @@ export interface Auth0ContextInterface * * If connecting the account is successful `onRedirectCallback` will be called * with the details of the connected account. + * + * @category Connected Accounts */ connectAccountWithRedirect: ( options: RedirectConnectAccountOptions @@ -262,6 +299,8 @@ export interface Auth0ContextInterface * the parameters provided as arguments, to clear the Auth0 session. * If the `logoutParams.federated` option is specified, it also clears the Identity Provider session. * [Read more about how Logout works at Auth0](https://auth0.com/docs/logout). + * + * @category Authentication */ logout: (options?: LogoutOptions) => Promise; @@ -291,6 +330,8 @@ export interface Auth0ContextInterface * * @param options - Optional parameters to identify which refresh token to revoke. * Defaults to the audience configured in `authorizationParams`. + * + * @category Tokens */ revokeRefreshToken: (options?: RevokeRefreshTokenOptions) => Promise; @@ -301,6 +342,8 @@ export interface Auth0ContextInterface * will be valid according to their expiration times. * * @param url The URL to that should be used to retrieve the `state` and `code` values. Defaults to `window.location.href` if not given. + * + * @category Authentication */ handleRedirectCallback: (url?: string) => Promise; @@ -316,6 +359,8 @@ export interface Auth0ContextInterface * @param id The identifier of a nonce: if absent, it will get the nonce * used for requests to Auth0. Otherwise, it will be used to * select a specific non-Auth0 nonce. + * + * @category Advanced */ getDpopNonce: Auth0Client['getDpopNonce']; @@ -328,6 +373,8 @@ export interface Auth0ContextInterface * @param id The identifier of a nonce: if absent, it will set the nonce * used for requests to Auth0. Otherwise, it will be used to * select a specific non-Auth0 nonce. + * + * @category Advanced */ setDpopNonce: Auth0Client['setDpopNonce']; @@ -336,6 +383,8 @@ export interface Auth0ContextInterface * key used to cryptographically bind access tokens with DPoP. * * It requires enabling the {@link Auth0ClientOptions.useDpop} option. + * + * @category Advanced */ generateDpopProof: Auth0Client['generateDpopProof']; @@ -346,6 +395,8 @@ export interface Auth0ContextInterface * headers or managing DPoP nonces and retries automatically. * * Check the `EXAMPLES.md` file for a deeper look into this method. + * + * @category Advanced */ createFetcher: Auth0Client['createFetcher']; @@ -357,6 +408,8 @@ export interface Auth0ContextInterface * * Returns a readonly copy of the initialization configuration * containing the domain and clientId. + * + * @category Advanced */ getConfiguration: Auth0Client['getConfiguration']; @@ -411,6 +464,8 @@ export interface Auth0ContextInterface * } * } * ``` + * + * @category Sub-clients */ mfa: MfaApiClient; @@ -427,6 +482,8 @@ export interface Auth0ContextInterface * * Both methods exchange the WebAuthn credential for Auth0 tokens and update * `isAuthenticated` / `user` in the same way as `loginWithPopup`. + * + * @category Sub-clients */ passkey: PasskeyApiClient; @@ -463,6 +520,8 @@ export interface Auth0ContextInterface * // Remove an authentication method * await myAccount.deleteAuthenticationMethod('method-id'); * ``` + * + * @category Sub-clients */ myAccount: MyAccountApiClient; @@ -478,6 +537,8 @@ export interface Auth0ContextInterface * - `getTokenSilently(options?)` - Get or renew an anonymous access token * - `logout()` - End the anonymous session and clear stored tokens * - `getClaims()` - Always returns `null` in EA + * + * @category Sub-clients */ anonymous: AnonymousSessionApiClient; @@ -555,6 +616,8 @@ export const initialContext = { /** * The Auth0 Context + * + * @category Context */ const Auth0Context = createContext(initialContext); diff --git a/src/auth0-provider.tsx b/src/auth0-provider.tsx index bd9331e6..1fe693d4 100644 --- a/src/auth0-provider.tsx +++ b/src/auth0-provider.tsx @@ -41,12 +41,16 @@ import { initialAuthState, type AuthState } from './auth-state'; /** * The account that has been connected during the connect flow. + * + * @category Reference */ export type ConnectedAccount = Omit; /** * The state of the application before the user was redirected to the login page * and any account that the user may have connected to. + * + * @category Getting Started */ export type AppState = { returnTo?: string; @@ -103,12 +107,16 @@ type Auth0ProviderBaseOptions = { /** * Options for `Auth0Provider` when configuring Auth0 via `domain` and `clientId`. * Use this type when building wrapper components around `Auth0Provider`. + * + * @category Getting Started */ export type Auth0ProviderWithConfigOptions = Auth0ProviderBaseOptions & Auth0ClientOptions & { client?: never }; /** * Options for `Auth0Provider` when supplying a pre-configured `Auth0Client` instance. + * + * @category Getting Started */ export type Auth0ProviderWithClientOptions = Auth0ProviderBaseOptions & { client: Auth0Client }; @@ -118,6 +126,8 @@ export type Auth0ProviderWithClientOptions = * * Either provide `domain` and `clientId` (`Auth0ProviderWithConfigOptions`) * or a pre-configured `client` instance (`Auth0ProviderWithClientOptions`). + * + * @category Getting Started */ export type Auth0ProviderOptions = | Auth0ProviderWithConfigOptions @@ -199,6 +209,8 @@ const createInitDeferred = (): InitDeferred => { * `'use client'` directive), so in a Next.js App Router app you can import and render * `Auth0Provider` directly from a Server Component such as `app/layout.tsx`. * Components that call hooks like `useAuth0` must still be Client Components. + * + * @category Getting Started */ const Auth0Provider = (opts: Auth0ProviderOptions) => { const { diff --git a/src/errors.tsx b/src/errors.tsx index 5f242b25..60a4953f 100644 --- a/src/errors.tsx +++ b/src/errors.tsx @@ -3,6 +3,8 @@ * be the error code. And possibly an `error_description` property * * See: https://openid.net/specs/openid-connect-core-1_0.html#rfc.section.3.1.2.6 + * + * @category Errors */ export class OAuthError extends Error { constructor(public error: string, public error_description?: string) { diff --git a/src/index.tsx b/src/index.tsx index e8066357..eb3530eb 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,3 +1,14 @@ +/** + * @module + * + * @categoryDescription Reference + * Supporting option, claim and parameter types that come from the underlying + * `@auth0/auth0-spa-js` SDK. You arrive at one of these from the hook or context + * member that uses it, rather than by browsing this section. For deeper + * documentation on each, see the + * [@auth0/auth0-spa-js reference](https://auth0.github.io/auth0-spa-js/). + */ + export { default as Auth0Provider, Auth0ProviderOptions, diff --git a/src/use-auth0-suspense.tsx b/src/use-auth0-suspense.tsx index b86c3418..48ac9c7a 100644 --- a/src/use-auth0-suspense.tsx +++ b/src/use-auth0-suspense.tsx @@ -11,6 +11,8 @@ import Auth0Context, { Auth0ContextInterface } from './auth0-context'; * The value returned by `useAuth0Suspense`: the full `useAuth0` interface minus * `isLoading` and the internal `_initPromise`. `error` is * retained for post-init failures such as `loginWithPopup`. + * + * @category Context */ export type Auth0SuspenseContextInterface = Omit< Auth0ContextInterface, @@ -39,6 +41,8 @@ export type Auth0SuspenseContextInterface = Omit< * renders if that check succeeded, or throws again if it did not. * * TUser is an optional type param to provide a type to the `user` field. + * + * @category Hooks & HOCs */ const useAuth0Suspense = ( context = Auth0Context diff --git a/src/use-auth0.tsx b/src/use-auth0.tsx index 372e3e44..9eee1fe4 100644 --- a/src/use-auth0.tsx +++ b/src/use-auth0.tsx @@ -4,27 +4,24 @@ import Auth0Context, { Auth0ContextInterface } from './auth0-context'; /** * ```js - * const { - * // Auth state: - * error, - * isAuthenticated, - * isLoading, - * user, - * // Auth methods: - * getAccessTokenSilently, - * getAccessTokenWithPopup, - * getIdTokenClaims, - * loginWithCustomTokenExchange, - * exchangeToken, // deprecated - use loginWithCustomTokenExchange - * loginWithRedirect, - * loginWithPopup, - * logout, - * } = useAuth0(); + * const { isLoading, isAuthenticated, user, loginWithRedirect, logout } = + * useAuth0(); * ``` * * Use the `useAuth0` hook in your components to access the auth state and methods. * + * It returns an {@link Auth0ContextInterface}, which carries the auth state + * (`isLoading`, `isAuthenticated`, `user`, `error`), the login, logout and token + * methods, and the four sub-clients: + * {@link Auth0ContextInterface.mfa | mfa}, + * {@link Auth0ContextInterface.passkey | passkey}, + * {@link Auth0ContextInterface.myAccount | myAccount} and + * {@link Auth0ContextInterface.anonymous | anonymous}. See that interface for + * the full set of members. + * * TUser is an optional type param to provide a type to the `user` field. + * + * @category Hooks & HOCs */ const useAuth0 = ( context = Auth0Context diff --git a/src/use-enterprise-connect.tsx b/src/use-enterprise-connect.tsx index 4b0e2496..7cd2e332 100644 --- a/src/use-enterprise-connect.tsx +++ b/src/use-enterprise-connect.tsx @@ -10,6 +10,8 @@ import Auth0Context, { /** * The shape returned by the `useEnterpriseConnect` hook. + * + * @category Hooks & HOCs */ export interface UseEnterpriseConnect { /** @@ -42,6 +44,8 @@ export interface UseEnterpriseConnect { * the Auth0 domain from the `Auth0Provider` configuration, so callers pass * only the email domain. `loginWithSSO` is sugar over `loginWithRedirect` * that sets `login_hint` to the provided email. + * + * @category Hooks & HOCs */ const useEnterpriseConnect = ( context = Auth0Context diff --git a/src/with-auth0.tsx b/src/with-auth0.tsx index 6232e6f2..47a57266 100644 --- a/src/with-auth0.tsx +++ b/src/with-auth0.tsx @@ -3,6 +3,8 @@ import Auth0Context, { Auth0ContextInterface } from './auth0-context'; /** * Components wrapped in `withAuth0` will have an additional `auth0` prop + * + * @category Hooks & HOCs */ export interface WithAuth0Props { auth0: Auth0ContextInterface; @@ -25,6 +27,8 @@ export interface WithAuth0Props { * * Providing a context as the second argument allows you to configure the Auth0Provider the Auth0Context * should come from f you have multiple within your application. + * + * @category Hooks & HOCs */ const withAuth0 =

( Component: ComponentType

, diff --git a/src/with-authentication-required.tsx b/src/with-authentication-required.tsx index 41bc8bed..1df61ebe 100644 --- a/src/with-authentication-required.tsx +++ b/src/with-authentication-required.tsx @@ -29,6 +29,8 @@ const defaultReturnTo = (): string => { /** * Options for the withAuthenticationRequired Higher Order Component + * + * @category Hooks & HOCs */ export interface WithAuthenticationRequiredOptions { /** @@ -99,6 +101,8 @@ export interface WithAuthenticationRequiredOptions { * * When you wrap your components in this Higher Order Component and an anonymous user visits your component * they will be redirected to the login page; after login they will be returned to the page they were redirected from. + * + * @category Hooks & HOCs */ const withAuthenticationRequired =

( Component: ComponentType

, diff --git a/typedoc.docsv2.js b/typedoc.docsv2.js new file mode 100644 index 00000000..831c5410 --- /dev/null +++ b/typedoc.docsv2.js @@ -0,0 +1,29 @@ +// TypeDoc config for the artifact published to the docs-v2 repo. +// +// Reuses every filtering and categorization decision from `typedoc.js` so the +// Mintlify site documents exactly the same surface as the HTML site, but emits +// only the JSON artifact that Mintlify consumes. +// +// `out`, `cleanOutputDir`, `theme` and `customCss` are dropped deliberately. +// TypeDoc emits an HTML output for `out` independently of `--json`, so leaving +// it in would rebuild `docs/` as a side effect of building the artifact. +const { + out, + cleanOutputDir, + theme, + customCss, + ...shared +} = require('./typedoc.js'); + +module.exports = { + ...shared, + + // Staging directory whose layout mirrors docs-v2's `main/`, so publishing is a + // straight copy of two files with no path rewriting. + json: './mintlify/docsv2/sdk-artifacts/auth0-react.json', + + // This file is committed to docs-v2. Minified, a rebuild turns into a + // single-line diff of tens of thousands of changes; pretty-printed, it diffs + // as just the symbols that actually changed. + pretty: true +}; diff --git a/typedoc.js b/typedoc.js index 2800dff2..674fbecb 100644 --- a/typedoc.js +++ b/typedoc.js @@ -1,15 +1,81 @@ +const { + CATEGORY_ORDER, + DEFAULT_CATEGORY +} = require('./scripts/typedoc-plugin.js'); + module.exports = { - name: '@auth0/auth0-react', + // Document what the package actually exports. Pointing TypeDoc at `src/` + // instead made it expand every file in the tree, which pulled internal + // modules (the reducer, auth state, utils) into the reference. + entryPoints: ['./src/index.tsx'], + entryPointStrategy: 'resolve', + out: './docs/', - exclude: ['./src/utils.tsx', './src/reducer.tsx'], - excludeExternals: false, + readme: './README.md', + name: 'Auth0 React SDK', + cleanOutputDir: true, + + plugin: ['./scripts/typedoc-plugin.js'], + theme: 'auth0', + + // Keep the reference to the public surface. excludePrivate: true, + excludeProtected: true, + excludeInternal: true, + // Without this, every error class inherits Error's `stack`, `captureStackTrace` + // and `prepareStackTrace` from TypeScript's own lib types, and React's own + // types leak in through the component props. Almost the entire type surface is + // re-exported from `@auth0/auth0-spa-js` and is part of our public API, so + // only the TypeScript lib, React and other third-party packages are external. + excludeExternals: true, + externalPattern: [ + '**/node_modules/typescript/**', + '**/node_modules/@types/**', + '**/node_modules/react/**', + '**/node_modules/react-dom/**' + ], + // Note: no blanket `node_modules` exclusion here. `exclude` is matched against + // a symbol's declaration file, so excluding node_modules would also drop the + // options, errors and sub-clients we deliberately re-export from + // `@auth0/auth0-spa-js`. + exclude: [ + '**/__tests__/**/*', + '**/__mocks__/**/*', + '**/cypress/**/*', + './src/utils.tsx', + './src/reducer.tsx' + ], + + // Group the landing page and sidebar by category rather than by TypeScript + // kind, so readers see "Getting Started" before a wall of interfaces. + categorizeByGroup: false, + categoryOrder: CATEGORY_ORDER, + defaultCategory: DEFAULT_CATEGORY, + navigation: { + includeCategories: true, + includeGroups: false + }, + sort: ['kind', 'alphabetical'], + kindSortOrder: [ + 'Function', + 'Class', + 'Interface', + 'TypeAlias', + 'Enum', + 'Variable' + ], + hideGenerator: true, - readme: './README.md', + searchInComments: true, highlightLanguages: ['typescript', 'javascript', 'jsx', 'tsx', 'bash'], + visibilityFilters: { protected: false, inherited: true, - external: true, + external: true }, + + compilerOptions: { + skipLibCheck: true + } };