@grantry/nextjs 0.0.0-stage → 0.1.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.
@@ -0,0 +1,34 @@
1
+ /**
2
+ * The React provider, saying "nextjs" — and reading `NEXT_PUBLIC_GRANTRY_*`.
3
+ *
4
+ * ## Why this file exists at all
5
+ *
6
+ * Everything else in this package is a straight re-export of `@grantry/react`,
7
+ * on the principle that a second implementation is a second thing to drift. So
8
+ * a wrapper needs a reason, and it has two:
9
+ *
10
+ * ⚠️ A NEXT.JS APP AND A PLAIN REACT APP ARE INDISTINGUISHABLE ON THE WIRE
11
+ * UNLESS ONE OF THEM SAYS SO. They render the same provider, construct the same
12
+ * client and send the same requests. That is normally a virtue and here it is a
13
+ * problem: the development Overview's setup guide watches for the merchant's
14
+ * app to connect and then shows them how to sign a user up *in the framework
15
+ * that connected*. Without this, everyone who installed `@grantry/nextjs` would
16
+ * be shown a plain-React snippet — no `middleware.ts`, no server helpers — on
17
+ * the one screen whose whole job is knowing what they are building.
18
+ *
19
+ * ⚠️ AND IT IS NOT DETECTABLE AT RUNTIME. `__NEXT_DATA__` exists only in the
20
+ * Pages Router, `process.env.NEXT_RUNTIME` is server-only, and sniffing for
21
+ * either would make the answer depend on which router the merchant chose. The
22
+ * honest signal is which package they installed, and this is the file that
23
+ * knows.
24
+ *
25
+ * ⚠️ THE SECOND REASON IS THE ENVIRONMENT VARIABLES, WHICH ONLY THIS PACKAGE
26
+ * CAN READ. Where the application's pages are is its own code since 0242 —
27
+ * `signInUrl`, `signUpUrl`, the fallback and force redirect URLs and
28
+ * `afterSignOutUrl`, Clerk's names — and a Next.js application sets them as
29
+ * `NEXT_PUBLIC_GRANTRY_*`. They become props of the React provider here, under
30
+ * whatever props were passed, so the rules stay `@grantry/react`'s.
31
+ */
32
+ import { type GrantryProviderProps } from '@grantry/react';
33
+ export declare function GrantryProvider(props: GrantryProviderProps): import("react").JSX.Element;
34
+ //# sourceMappingURL=provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../src/provider.tsx"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,EAGL,KAAK,oBAAoB,EAE1B,MAAM,gBAAgB,CAAC;AAsBxB,wBAAgB,eAAe,CAAC,KAAK,EAAE,oBAAoB,+BAW1D"}
@@ -0,0 +1,66 @@
1
+ 'use client';
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ /**
4
+ * The React provider, saying "nextjs" — and reading `NEXT_PUBLIC_GRANTRY_*`.
5
+ *
6
+ * ## Why this file exists at all
7
+ *
8
+ * Everything else in this package is a straight re-export of `@grantry/react`,
9
+ * on the principle that a second implementation is a second thing to drift. So
10
+ * a wrapper needs a reason, and it has two:
11
+ *
12
+ * ⚠️ A NEXT.JS APP AND A PLAIN REACT APP ARE INDISTINGUISHABLE ON THE WIRE
13
+ * UNLESS ONE OF THEM SAYS SO. They render the same provider, construct the same
14
+ * client and send the same requests. That is normally a virtue and here it is a
15
+ * problem: the development Overview's setup guide watches for the merchant's
16
+ * app to connect and then shows them how to sign a user up *in the framework
17
+ * that connected*. Without this, everyone who installed `@grantry/nextjs` would
18
+ * be shown a plain-React snippet — no `middleware.ts`, no server helpers — on
19
+ * the one screen whose whole job is knowing what they are building.
20
+ *
21
+ * ⚠️ AND IT IS NOT DETECTABLE AT RUNTIME. `__NEXT_DATA__` exists only in the
22
+ * Pages Router, `process.env.NEXT_RUNTIME` is server-only, and sniffing for
23
+ * either would make the answer depend on which router the merchant chose. The
24
+ * honest signal is which package they installed, and this is the file that
25
+ * knows.
26
+ *
27
+ * ⚠️ THE SECOND REASON IS THE ENVIRONMENT VARIABLES, WHICH ONLY THIS PACKAGE
28
+ * CAN READ. Where the application's pages are is its own code since 0242 —
29
+ * `signInUrl`, `signUpUrl`, the fallback and force redirect URLs and
30
+ * `afterSignOutUrl`, Clerk's names — and a Next.js application sets them as
31
+ * `NEXT_PUBLIC_GRANTRY_*`. They become props of the React provider here, under
32
+ * whatever props were passed, so the rules stay `@grantry/react`'s.
33
+ */
34
+ import { GrantryProvider as ReactProvider, mergeRouteOptions, } from '@grantry/react';
35
+ /**
36
+ * `NEXT_PUBLIC_GRANTRY_*`, as route options.
37
+ *
38
+ * ⚠️ EVERY NAME WRITTEN OUT IN FULL. Next.js puts a `NEXT_PUBLIC_` variable into
39
+ * the browser bundle only where `process.env.NAME` appears literally; a name
40
+ * built from a prefix is correct on the server and `undefined` in the browser,
41
+ * which is a hydration mismatch as well as a wrong link.
42
+ */
43
+ function publicRouteOptions() {
44
+ return {
45
+ signInUrl: process.env.NEXT_PUBLIC_GRANTRY_SIGN_IN_URL,
46
+ signUpUrl: process.env.NEXT_PUBLIC_GRANTRY_SIGN_UP_URL,
47
+ signInFallbackRedirectUrl: process.env.NEXT_PUBLIC_GRANTRY_SIGN_IN_FALLBACK_REDIRECT_URL,
48
+ signUpFallbackRedirectUrl: process.env.NEXT_PUBLIC_GRANTRY_SIGN_UP_FALLBACK_REDIRECT_URL,
49
+ signInForceRedirectUrl: process.env.NEXT_PUBLIC_GRANTRY_SIGN_IN_FORCE_REDIRECT_URL,
50
+ signUpForceRedirectUrl: process.env.NEXT_PUBLIC_GRANTRY_SIGN_UP_FORCE_REDIRECT_URL,
51
+ afterSignOutUrl: process.env.NEXT_PUBLIC_GRANTRY_AFTER_SIGN_OUT_URL,
52
+ };
53
+ }
54
+ export function GrantryProvider(props) {
55
+ /*
56
+ * Props first, then the variables — Clerk's order — merged option by option,
57
+ * so a prop left undefined does not hide the variable beneath it.
58
+ *
59
+ * `props.sdk` after `sdk="nextjs"`, so it is genuinely a default rather than
60
+ * a lock. An application embedding this inside something else can still say
61
+ * what it is, and passing nothing — the normal case — reports `nextjs`.
62
+ */
63
+ const routes = mergeRouteOptions(props, publicRouteOptions());
64
+ return _jsx(ReactProvider, { sdk: "nextjs", ...props, ...routes });
65
+ }
66
+ //# sourceMappingURL=provider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider.js","sourceRoot":"","sources":["../src/provider.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,EACL,eAAe,IAAI,aAAa,EAChC,iBAAiB,GAGlB,MAAM,gBAAgB,CAAC;AAExB;;;;;;;GAOG;AACH,SAAS,kBAAkB;IACzB,OAAO;QACL,SAAS,EAAE,OAAO,CAAC,GAAG,CAAC,+BAA+B;QACtD,SAAS,EAAE,OAAO,CAAC,GAAG,CAAC,+BAA+B;QACtD,yBAAyB,EAAE,OAAO,CAAC,GAAG,CAAC,iDAAiD;QACxF,yBAAyB,EAAE,OAAO,CAAC,GAAG,CAAC,iDAAiD;QACxF,sBAAsB,EAAE,OAAO,CAAC,GAAG,CAAC,8CAA8C;QAClF,sBAAsB,EAAE,OAAO,CAAC,GAAG,CAAC,8CAA8C;QAClF,eAAe,EAAE,OAAO,CAAC,GAAG,CAAC,sCAAsC;KACpE,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,KAA2B;IACzD;;;;;;;OAOG;IACH,MAAM,MAAM,GAAG,iBAAiB,CAAC,KAAK,EAAE,kBAAkB,EAAE,CAAC,CAAC;IAC9D,OAAO,KAAC,aAAa,IAAC,GAAG,EAAC,QAAQ,KAAK,KAAK,KAAM,MAAM,GAAI,CAAC;AAC/D,CAAC"}
@@ -0,0 +1,64 @@
1
+ import { TokenVerificationError, verifyToken, type VerifiedToken, type VerifyFailureReason, type VerifyTokenOptions } from '@grantry/react/server';
2
+ export interface AuthObject {
3
+ /** Null when the visitor is not signed in. */
4
+ userId: string | null;
5
+ sessionId: string | null;
6
+ environmentId: string | null;
7
+ emailVerified: boolean;
8
+ /**
9
+ * The operator, when this visitor is somebody from your team viewing as the user — the verified
10
+ * `act` claim. Refuse your own sensitive actions while it is set.
11
+ */
12
+ actor: {
13
+ sub: string;
14
+ } | null;
15
+ /** The verified token, for callers that want the expiry or full claims. */
16
+ token: VerifiedToken | null;
17
+ /**
18
+ * Why authentication failed, when it did.
19
+ *
20
+ * `expired` in particular is worth distinguishing: it usually means the tab
21
+ * was asleep and the client will recover on its own, not that anything is
22
+ * misconfigured.
23
+ */
24
+ reason: TokenVerificationError['reason'] | null;
25
+ }
26
+ export interface AuthOptions {
27
+ /** Override for self-hosted deployments and local development. */
28
+ apiUrl?: string;
29
+ /**
30
+ * Your publishable key — the same one the provider takes.
31
+ *
32
+ * ⚠️ IT IS WHAT SAYS WHICH APPLICATION A TOKEN MUST BELONG TO. Read from
33
+ * `GRANTRY_PUBLISHABLE_KEY`, then `NEXT_PUBLIC_GRANTRY_PUBLISHABLE_KEY` — the
34
+ * variable `<GrantryProvider>` is already given — when omitted, so the
35
+ * ordinary Next.js application passes nothing here. With no key and no
36
+ * `environmentId`, `auth()` throws `no_audience` rather than accepting a token
37
+ * minted for another application. See `@grantry/react/server`.
38
+ */
39
+ publishableKey?: string;
40
+ /** The environment id, for a deployment that configures it directly. */
41
+ environmentId?: string;
42
+ }
43
+ /**
44
+ * The current visitor, in a server component, route handler or server action.
45
+ *
46
+ * Never throws for an unauthenticated visitor — being signed out is an ordinary
47
+ * state, not an exception. The `reason` field carries the detail when a token
48
+ * was present but unusable.
49
+ */
50
+ export declare function auth(options?: AuthOptions): Promise<AuthObject>;
51
+ /**
52
+ * `auth()`, but throws when nobody is signed in.
53
+ *
54
+ * For routes where being signed out is a bug rather than a case to handle —
55
+ * it narrows `userId` to a string, so the call site needs no null check and
56
+ * cannot forget one.
57
+ */
58
+ export declare function requireAuth(options?: AuthOptions): Promise<AuthObject & {
59
+ userId: string;
60
+ sessionId: string;
61
+ }>;
62
+ export { TokenVerificationError, verifyToken, type VerifiedToken, type VerifyFailureReason, type VerifyTokenOptions, };
63
+ export { ReverificationCheckError, verifyReverification, type VerifiedReverification, } from '@grantry/react/server';
64
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAkBA,OAAO,EACL,sBAAsB,EACtB,WAAW,EACX,KAAK,aAAa,EAClB,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,EACxB,MAAM,uBAAuB,CAAC;AAG/B,MAAM,WAAW,UAAU;IACzB,8CAA8C;IAC9C,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,aAAa,EAAE,OAAO,CAAC;IACvB;;;OAGG;IACH,KAAK,EAAE;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAC9B,2EAA2E;IAC3E,KAAK,EAAE,aAAa,GAAG,IAAI,CAAC;IAC5B;;;;;;OAMG;IACH,MAAM,EAAE,sBAAsB,CAAC,QAAQ,CAAC,GAAG,IAAI,CAAC;CACjD;AAYD,MAAM,WAAW,WAAW;IAC1B,kEAAkE;IAClE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;;;;;OASG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,wEAAwE;IACxE,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;GAMG;AACH,wBAAsB,IAAI,CAAC,OAAO,GAAE,WAAgB,GAAG,OAAO,CAAC,UAAU,CAAC,CA6BzE;AAED;;;;;;GAMG;AACH,wBAAsB,WAAW,CAC/B,OAAO,GAAE,WAAgB,GACxB,OAAO,CAAC,UAAU,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC,CAW7D;AAMD,OAAO,EACL,sBAAsB,EACtB,WAAW,EACX,KAAK,aAAa,EAClB,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,GACxB,CAAC;AAMF,OAAO,EACL,wBAAwB,EACxB,oBAAoB,EACpB,KAAK,sBAAsB,GAC5B,MAAM,uBAAuB,CAAC"}
package/dist/server.js ADDED
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Server-side auth for the Next.js App Router — `@grantry/nextjs/server`.
3
+ *
4
+ * const { userId } = await auth();
5
+ * if (!userId) redirect('/sign-in');
6
+ *
7
+ * Reads the access-token cookie the client SDK mirrors onto this app's domain
8
+ * and verifies it against Grantry's published keys. No network call in the hot
9
+ * path once the keys are cached, so this is cheap enough to use on every render.
10
+ *
11
+ * ⚠️ `auth()` IS THE APP ROUTER'S ALONE, SO THE VERIFIER IS EXPORTED BESIDE IT. It reads
12
+ * `next/headers`, which a Pages Router `getServerSideProps` or API route cannot call; those verify
13
+ * `req.cookies.grantry_token` with `verifyToken` themselves. The docs said "verify in
14
+ * getServerSideProps" while this entry point offered nothing to verify with, and a Next.js
15
+ * application is told not to install `@grantry/react` beside this package (the docs audit,
16
+ * 1 October 2026).
17
+ */
18
+ import { cookies } from 'next/headers';
19
+ import { TokenVerificationError, verifyToken, } from '@grantry/react/server';
20
+ import { TOKEN_COOKIE_NAME, tokenFromCookie } from './cookie.js';
21
+ const SIGNED_OUT = {
22
+ userId: null,
23
+ sessionId: null,
24
+ environmentId: null,
25
+ emailVerified: false,
26
+ actor: null,
27
+ token: null,
28
+ reason: null,
29
+ };
30
+ /**
31
+ * The current visitor, in a server component, route handler or server action.
32
+ *
33
+ * Never throws for an unauthenticated visitor — being signed out is an ordinary
34
+ * state, not an exception. The `reason` field carries the detail when a token
35
+ * was present but unusable.
36
+ */
37
+ export async function auth(options = {}) {
38
+ const store = await cookies();
39
+ const raw = tokenFromCookie(store.get(TOKEN_COOKIE_NAME));
40
+ if (!raw)
41
+ return SIGNED_OUT;
42
+ try {
43
+ const token = await verifyToken(raw, options);
44
+ return {
45
+ userId: token.userId,
46
+ sessionId: token.sessionId,
47
+ environmentId: token.environmentId,
48
+ emailVerified: token.emailVerified,
49
+ actor: token.actor,
50
+ token,
51
+ reason: null,
52
+ };
53
+ }
54
+ catch (cause) {
55
+ /*
56
+ * ⚠️ `no_audience` IS RETHROWN RATHER THAN REPORTED AS SIGNED OUT. It says
57
+ * this deployment never configured which application to accept tokens for,
58
+ * which is a fault in the app rather than a fact about the visitor —
59
+ * swallowing it would sign every user out at once and look like a broken
60
+ * cookie.
61
+ */
62
+ if (cause instanceof TokenVerificationError && cause.reason !== 'no_audience') {
63
+ return { ...SIGNED_OUT, reason: cause.reason };
64
+ }
65
+ throw cause;
66
+ }
67
+ }
68
+ /**
69
+ * `auth()`, but throws when nobody is signed in.
70
+ *
71
+ * For routes where being signed out is a bug rather than a case to handle —
72
+ * it narrows `userId` to a string, so the call site needs no null check and
73
+ * cannot forget one.
74
+ */
75
+ export async function requireAuth(options = {}) {
76
+ const result = await auth(options);
77
+ if (!result.userId || !result.sessionId) {
78
+ throw new Error('requireAuth() was called but no user is signed in. ' +
79
+ 'Use auth() and handle the signed-out case, or protect this route in middleware.');
80
+ }
81
+ return result;
82
+ }
83
+ /*
84
+ * `verifyToken` for the Pages Router and anything else `auth()` cannot reach — see the note at the
85
+ * top. The same function `@grantry/react/server` exports, not a copy.
86
+ */
87
+ export { TokenVerificationError, verifyToken, };
88
+ /*
89
+ * Spending a "Verify it's you" grant before a sensitive action (0330), with `auth()`'s `sessionId`
90
+ * and `userId` — the same function `@grantry/react/server` exports, for the reason above.
91
+ */
92
+ export { ReverificationCheckError, verifyReverification, } from '@grantry/react/server';
93
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EACL,sBAAsB,EACtB,WAAW,GAIZ,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAyB9D,MAAM,UAAU,GAAe;IAC7B,MAAM,EAAE,IAAI;IACZ,SAAS,EAAE,IAAI;IACf,aAAa,EAAE,IAAI;IACnB,aAAa,EAAE,KAAK;IACpB,KAAK,EAAE,IAAI;IACX,KAAK,EAAE,IAAI;IACX,MAAM,EAAE,IAAI;CACb,CAAC;AAoBF;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,IAAI,CAAC,UAAuB,EAAE;IAClD,MAAM,KAAK,GAAG,MAAM,OAAO,EAAE,CAAC;IAC9B,MAAM,GAAG,GAAG,eAAe,CAAC,KAAK,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC,CAAC;IAC1D,IAAI,CAAC,GAAG;QAAE,OAAO,UAAU,CAAC;IAE5B,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,MAAM,WAAW,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;QAC9C,OAAO;YACL,MAAM,EAAE,KAAK,CAAC,MAAM;YACpB,SAAS,EAAE,KAAK,CAAC,SAAS;YAC1B,aAAa,EAAE,KAAK,CAAC,aAAa;YAClC,aAAa,EAAE,KAAK,CAAC,aAAa;YAClC,KAAK,EAAE,KAAK,CAAC,KAAK;YAClB,KAAK;YACL,MAAM,EAAE,IAAI;SACb,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf;;;;;;WAMG;QACH,IAAI,KAAK,YAAY,sBAAsB,IAAI,KAAK,CAAC,MAAM,KAAK,aAAa,EAAE,CAAC;YAC9E,OAAO,EAAE,GAAG,UAAU,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC;QACjD,CAAC;QACD,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,UAAuB,EAAE;IAEzB,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,CAAC;IAEnC,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC;QACxC,MAAM,IAAI,KAAK,CACb,qDAAqD;YACnD,iFAAiF,CACpF,CAAC;IACJ,CAAC;IAED,OAAO,MAA4D,CAAC;AACtE,CAAC;AAED;;;GAGG;AACH,OAAO,EACL,sBAAsB,EACtB,WAAW,GAIZ,CAAC;AAEF;;;GAGG;AACH,OAAO,EACL,wBAAwB,EACxB,oBAAoB,GAErB,MAAM,uBAAuB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,64 @@
1
1
  {
2
2
  "name": "@grantry/nextjs",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.1.0",
4
+ "license": "MIT",
5
+ "type": "module",
6
+ "description": "Grantry authentication for Next.js: the React components and hooks, server-side auth() and route-protecting middleware.",
7
+ "keywords": [
8
+ "grantry",
9
+ "nextjs",
10
+ "next",
11
+ "authentication",
12
+ "auth",
13
+ "middleware",
14
+ "react"
15
+ ],
16
+ "homepage": "https://grantry.dev",
17
+ "engines": {
18
+ "node": ">=18.18.0"
19
+ },
20
+ "sideEffects": false,
21
+ "exports": {
22
+ ".": {
23
+ "types": "./dist/index.d.ts",
24
+ "default": "./dist/index.js"
25
+ },
26
+ "./server": {
27
+ "types": "./dist/server.d.ts",
28
+ "default": "./dist/server.js"
29
+ },
30
+ "./middleware": {
31
+ "types": "./dist/middleware.d.ts",
32
+ "default": "./dist/middleware.js"
33
+ },
34
+ "./package.json": "./package.json"
35
+ },
36
+ "publishConfig": {
37
+ "access": "public"
38
+ },
39
+ "files": [
40
+ "dist",
41
+ "src",
42
+ "README.md",
43
+ "LICENSE"
44
+ ],
45
+ "dependencies": {
46
+ "@grantry/react": "0.1.0"
47
+ },
48
+ "peerDependencies": {
49
+ "next": "^15.0.0 || ^16.0.0",
50
+ "react": "^18.0.0 || ^19.0.0"
51
+ },
52
+ "devDependencies": {
53
+ "@types/react": "^19.0.2",
54
+ "next": "^16.3.1",
55
+ "react": "^19.2.8",
56
+ "typescript": "5.9.3"
57
+ },
58
+ "scripts": {
59
+ "build": "node build.mjs",
60
+ "typecheck": "tsc --noEmit",
61
+ "test": "node ../sdk-react/node_modules/vitest/vitest.mjs run"
62
+ },
63
+ "types": "./dist/index.d.ts"
6
64
  }
package/src/cookie.ts ADDED
@@ -0,0 +1,24 @@
1
+ /**
2
+ * The access-token cookie, as the middleware and `auth()` both read it.
3
+ *
4
+ * ⚠️ ONE COPY OF THE NAME, AND A TEST HOLDS IT TO `@grantry/js`. It cannot be imported from there:
5
+ * this package depends on `@grantry/react` alone, and `@grantry/react`'s main entry is the browser
6
+ * half, which has no business in a middleware bundle. `server.ts` and `middleware.ts` each carried
7
+ * their own literal, one under a comment saying it was imported; `tests/cookie.test.ts` compares
8
+ * this one with the writer's.
9
+ */
10
+ export const TOKEN_COOKIE_NAME = 'grantry_token';
11
+
12
+ /**
13
+ * The token in the cookie as Next.js hands it over, or null when there is none.
14
+ *
15
+ * ⚠️ NOT DECODED A SECOND TIME. Next's cookie parser already applies `decodeURIComponent` to every
16
+ * value, and both readers applied it again. A JWT never contains `%`, so the second pass changed
17
+ * nothing for a real token — and threw `URIError` for a cookie whose decoded value held a stray
18
+ * `%E0`. In `auth()` that error was not a `TokenVerificationError`, so it was rethrown and every
19
+ * server component calling `auth()` failed to render for that visitor. Anything other than a token
20
+ * that is still left is reported by `verifyToken` as `malformed`.
21
+ */
22
+ export function tokenFromCookie(cookie: { value: string } | undefined): string | null {
23
+ return cookie?.value || null;
24
+ }
package/src/index.ts ADDED
@@ -0,0 +1,249 @@
1
+ /**
2
+ * @grantry/nextjs — authentication for Next.js.
3
+ *
4
+ * app/layout.tsx <GrantryProvider> (client)
5
+ * app/page.tsx await auth() (server)
6
+ * middleware.ts grantryMiddleware() (edge)
7
+ *
8
+ * ⚠️ A RE-EXPORT, NOT A SECOND IMPLEMENTATION. Everything below is the React
9
+ * SDK unchanged, so a Next.js application installs one dependency and imports
10
+ * the same names it would in any React app — and there is no second copy of a
11
+ * component to drift from the first. The Next-specific half is `./server` and
12
+ * `./middleware`, which is genuinely different code.
13
+ *
14
+ * The one exception is `GrantryProvider`, which is a four-line wrapper around
15
+ * the React one for the sole purpose of saying "nextjs" rather than "react" on
16
+ * the wire. See `./provider` for why that distinction has to exist at all.
17
+ *
18
+ * ⚠️ EVERY NAME `@grantry/react` EXPORTS IS LISTED HERE, AND A TEST HOLDS IT TO
19
+ * THAT. The list is by name, with no `export *`, so for months each new React
20
+ * export was simply absent from Next.js:
21
+ * `AuthCard`, `OAuthConsent`, the localization contract and the entitlement
22
+ * helpers all had documentation pages telling Next.js readers to import them
23
+ * from here. `packages/sdk-react/tests/nextjs-exports.test.ts` compares the two
24
+ * lists, types included, and fails on the next one.
25
+ */
26
+
27
+ export { GrantryProvider } from './provider';
28
+
29
+ export {
30
+ // Hooks
31
+ useAuth,
32
+ useUser,
33
+ useSession,
34
+ useAccounts,
35
+ useOAuth,
36
+ useGrantry,
37
+ useEntitlements,
38
+ readOAuthResult,
39
+ // The public plans, for pricing cards of your own
40
+ usePlans,
41
+ pricingCatalogue,
42
+ formatMoney,
43
+
44
+ // Where the application's own pages are: the provider's route options and their helpers.
45
+ useRoutes,
46
+ mergeRouteOptions,
47
+ redirectTarget,
48
+ requestedRedirectUrl,
49
+
50
+ // Headless flows
51
+ useSignInFlow,
52
+ useSignUpFlow,
53
+
54
+ // Entitlements, read straight off an access token
55
+ entitlementsFromToken,
56
+ actorFromToken,
57
+ checkEntitlements,
58
+ EMPTY_ENTITLEMENTS,
59
+
60
+ // Control
61
+ SignedIn,
62
+ SignedOut,
63
+ GrantryLoading,
64
+ GrantryLoaded,
65
+ /*
66
+ * ⚠️ THE CONNECTION PAIR, WHICH IS NOT THE SESSION PAIR. See the React
67
+ * package's `control.tsx` for why they are separate rather than four states
68
+ * of one machine the way Clerk's are.
69
+ */
70
+ GrantryDegraded,
71
+ GrantryFailed,
72
+ useEnvironmentStatus,
73
+ Protect,
74
+
75
+ // Unstyled actions
76
+ SignOutButton,
77
+ useSignOutAction,
78
+
79
+ // Drop-in components. Cut from a release and back; see the React package's
80
+ // index for what changed on the way back in (the default look, not the code).
81
+ SignIn,
82
+ SignUp,
83
+ /*
84
+ * ⚠️ RE-EXPORTED BY NAME, SO A NEW COMPONENT IS UNREACHABLE FROM NEXT.JS
85
+ * UNTIL IT IS LISTED HERE. `<SignIn />` draws this itself when the account
86
+ * owes something, but an application that redirects somewhere other than its
87
+ * sign-in route has to render it there.
88
+ */
89
+ CompleteAccount,
90
+ UserButton,
91
+ /*
92
+ * ⚠️ NOT A COMPONENT, AND IT HAS TO BE HERE ANYWAY. `<UserButton />` sends
93
+ * "Add account" to the sign-in page with a marker on the link, and a Next.js
94
+ * application cannot let that visitor past its own gate without this. The
95
+ * list above is by name, so an unlisted helper is the same as one that does
96
+ * not exist -- and the failure is a redirect loop in somebody else's app.
97
+ */
98
+ isAddingAccount,
99
+ addAccountUrl,
100
+ ADD_ACCOUNT_PARAM,
101
+ /*
102
+ * ⚠️ FOUR COMPONENTS THAT WERE EXPORTED FROM REACT AND UNREACHABLE FROM HERE,
103
+ * which is the same as not existing for a Next.js application — this list is
104
+ * by name and there is no deep-import escape hatch.
105
+ */
106
+ UserAvatar,
107
+ Waitlist,
108
+ ResetPassword,
109
+ SetupMfa,
110
+ UserProfile,
111
+ PricingTable,
112
+ CheckoutButton,
113
+ PlanDetailsButton,
114
+ SubscriptionDetailsButton,
115
+ ProductCard,
116
+ type ProductCardProps,
117
+ MenuItems,
118
+ CustomAction,
119
+ CustomLink,
120
+ CustomPage,
121
+ // The card itself, the consent card, "Continue as" and the support-visit banner.
122
+ AuthCard,
123
+ OAuthConsent,
124
+ ContinueAs,
125
+ ImpersonationBanner,
126
+
127
+ // Localization
128
+ LocalizationProvider,
129
+ useTranslate,
130
+ EN,
131
+
132
+ // Appearance
133
+ AppearanceProvider,
134
+ GRANTRY_STYLES,
135
+ useStylesheet,
136
+ variablesToStyle,
137
+
138
+ // The brand theme: what Hosted sign-in → Theme stores, the hosted screens' look
139
+ THEME_COLORS,
140
+ THEME_COLOR_LABELS,
141
+ THEME_FONTS,
142
+ THEME_LOGO_PLACEMENTS,
143
+ THEME_RADII,
144
+ THEME_SOCIAL_PLACEMENTS,
145
+ THEME_SOCIAL_VARIANTS,
146
+ brandThemeAppearance,
147
+ cleanBrandTheme,
148
+ themeColorScheme,
149
+
150
+ // Avatars, drawn as the components draw them
151
+ DEFAULT_AVATAR_COLORS,
152
+ avatarBackgroundCss,
153
+ avatarInitial,
154
+ avatarSeed,
155
+
156
+ // The client
157
+ GrantryClient,
158
+ GrantryError,
159
+ TOKEN_COOKIE_NAME,
160
+
161
+ // "Verify it's you" (0330): the dialog before a sensitive action, and what it resolves with.
162
+ useReverification,
163
+ type Reverify,
164
+ type ReverifyOptions,
165
+ type Reverification,
166
+ type ReverificationGrant,
167
+ type ReverificationMethod,
168
+
169
+ type GrantryProviderProps,
170
+ type GrantryPreview,
171
+ type SignInHostProviderProps,
172
+ type ResellerHostProviderProps,
173
+ type Routes,
174
+ type RouteOptions,
175
+ type RedirectTargets,
176
+ type TokenEntitlements,
177
+ type HasParams,
178
+ type UseEntitlementsReturn,
179
+ type SignInStatus,
180
+ type SignUpStatus,
181
+ type ProtectProps,
182
+ type AuthCardProps,
183
+ type OAuthConsentProps,
184
+ type ContinueAsProps,
185
+ type Localization,
186
+ type LocalizationInput,
187
+ type GrantryClientOptions,
188
+ type SignUpParams,
189
+ type SignUpResult,
190
+ type BackupCodesProof,
191
+ type BackupCodeEntry,
192
+ type RevealedBackupCodes,
193
+ type ChangeQuote,
194
+ type CouponPreview,
195
+ type FieldIssue,
196
+ type BrandTheme,
197
+ type ThemeColor,
198
+ type ThemeFont,
199
+ type ThemeProblem,
200
+ type ThemeRadius,
201
+ type AvatarStyle,
202
+ type AvatarBackground,
203
+ type AvatarForeground,
204
+ type GrantryUser,
205
+ type GrantrySession,
206
+ type GrantryTrustedDevice,
207
+ type GrantryBoundDevice,
208
+ type GrantryBoundDevices,
209
+ type GrantryAccount,
210
+ type GrantryEnvironment,
211
+ type GrantryErrorCode,
212
+ type SignInResult,
213
+ type Appearance,
214
+ type ElementDescriptor,
215
+ type ElementsConfig,
216
+ type ElementStyle,
217
+ type AppearanceVariables,
218
+ type AppearanceLayout,
219
+ type EnvironmentStatus,
220
+ type SignOutButtonProps,
221
+ type UseSignOutAction,
222
+ type SignInProps,
223
+ type SignUpProps,
224
+ type CompleteAccountProps,
225
+ type UserButtonProps,
226
+ type UserProfileProps,
227
+ type UseAuthReturn,
228
+ type UseUserReturn,
229
+ type UseSessionReturn,
230
+ type UseAccountsReturn,
231
+ type UseOAuthReturn,
232
+ type UseSignInFlow,
233
+ type UseSignUpFlow,
234
+ type OAuthProviderInfo,
235
+ type ConnectedAccount,
236
+ type GrantryAuthorizedApp,
237
+ type MfaFactor,
238
+ type UserEmailAddress,
239
+ type PublicPlan,
240
+ type PlanPrice,
241
+ type PlansResponse,
242
+ type BillingSummary,
243
+ type UsePlansOptions,
244
+ type UsePlansReturn,
245
+ type PricingCatalogue,
246
+ type PricingPlan,
247
+ type PricingPlanPrice,
248
+ type PlanIntervalUnit,
249
+ } from '@grantry/react';