fontdue-js 3.2.0 → 3.2.2
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.
- package/CHANGELOG.md +11 -0
- package/dist/__tests__/nextAdapter.test.js +63 -3
- package/dist/__tests__/recaptchaTimeout.test.js +191 -0
- package/dist/components/ConnectionErrorToolbar.js +2 -2
- package/dist/components/FontdueAdminToolbar/index.js +23 -11
- package/dist/components/NewsletterSignup/index.js +33 -5
- package/dist/components/NodePasswordForm/index.js +9 -4
- package/dist/components/Recaptcha.d.ts +3 -0
- package/dist/components/Recaptcha.js +38 -0
- package/dist/components/TestFontsForm/index.js +33 -5
- package/dist/components/TypeTester/TypeTesterStandaloneElement.js +9 -4
- package/dist/components/TypeTester/TypeTesterVariableAxes.js +16 -0
- package/dist/components/elements/StoreModalUnifiedCheckout.js +48 -6
- package/dist/fontdue.css +149 -25
- package/dist/hooks/useRecaptchaTimeout.d.ts +11 -0
- package/dist/hooks/useRecaptchaTimeout.js +60 -0
- package/dist/next/unlock.js +64 -1
- package/dist/relay/environment.js +1 -1
- package/package.json +1 -1
- package/types/next-headers.d.ts +21 -1
package/dist/next/unlock.js
CHANGED
|
@@ -22,16 +22,44 @@
|
|
|
22
22
|
// forged POST reveals nothing and buys the caller only their own dynamic
|
|
23
23
|
// renders, exactly like a forged preview POST.
|
|
24
24
|
//
|
|
25
|
+
// The bypass is scoped to the unlocked page's path, not the whole site.
|
|
26
|
+
// draftMode().enable() sets the cookie with Path=/, which would take EVERY
|
|
27
|
+
// page this visitor touches off the full-route cache — and because a render
|
|
28
|
+
// carrying a node-access token also opts all of its GraphQL fetches out of the
|
|
29
|
+
// shared data cache, one unlock made the visitor's whole browsing session
|
|
30
|
+
// dynamically rendered and measurably slow (seconds per page on heavy pages).
|
|
31
|
+
// Draft mode is decided per-request by whether the browser SENDS the bypass
|
|
32
|
+
// cookie, and the browser decides that by cookie Path — so the form posts the
|
|
33
|
+
// pathname it unlocked on and we narrow the cookie to it. Public pages stay on
|
|
34
|
+
// the static cache; only the unlocked collection's pages render dynamically
|
|
35
|
+
// for this visitor. Unlocking several collections yields several same-name
|
|
36
|
+
// cookies with different paths, which browsers store and send independently.
|
|
37
|
+
// (Admin preview keeps its site-wide Path=/ bypass — previewing the whole site
|
|
38
|
+
// dynamically is the point there.)
|
|
39
|
+
//
|
|
25
40
|
// The bypass cookie is a session cookie, while the node-access cookie lasts ~30
|
|
26
41
|
// days. A returning visitor whose bypass lapsed sees the static form again and
|
|
27
42
|
// re-enters the password; re-unlocking re-enables the bypass. There is no
|
|
28
43
|
// DELETE: draft mode is shared with admin preview, so an unlock exit must not
|
|
29
44
|
// tear down an active preview — the bypass simply expires with the session.
|
|
30
45
|
|
|
31
|
-
import { draftMode } from 'next/headers';
|
|
46
|
+
import { cookies, draftMode } from 'next/headers';
|
|
32
47
|
|
|
33
48
|
/** Default path the templates mount the handler at. */
|
|
34
49
|
export const UNLOCK_ENDPOINT = '/api/unlock';
|
|
50
|
+
|
|
51
|
+
// Next's draft-mode bypass cookie. The name is Next's stable, documented
|
|
52
|
+
// public contract (the same one draftMode().enable() sets), but it isn't
|
|
53
|
+
// exported from a public entry point, so it's spelled out here.
|
|
54
|
+
const PRERENDER_BYPASS_COOKIE = '__prerender_bypass';
|
|
55
|
+
|
|
56
|
+
// A client-sent pathname we are willing to put in a Set-Cookie Path attribute:
|
|
57
|
+
// absolute, bounded, visible ASCII with no ';' — i.e. what
|
|
58
|
+
// window.location.pathname (always percent-encoded) actually produces, and
|
|
59
|
+
// nothing that could terminate or escape the attribute (Next serializes the
|
|
60
|
+
// path verbatim). Anything else falls back to the site-wide default.
|
|
61
|
+
const SCOPE_PATH_MAX_LENGTH = 512;
|
|
62
|
+
const SCOPE_PATH_RE = /^\/[\x21-\x3a\x3c-\x7e]*$/;
|
|
35
63
|
export async function POST(request) {
|
|
36
64
|
const body = await request.json().catch(() => ({}));
|
|
37
65
|
if (typeof body.token !== 'string' || body.token === '') {
|
|
@@ -43,7 +71,42 @@ export async function POST(request) {
|
|
|
43
71
|
});
|
|
44
72
|
}
|
|
45
73
|
(await draftMode()).enable();
|
|
74
|
+
await scopeBypassCookie(scopePath(body.path));
|
|
46
75
|
return Response.json({
|
|
47
76
|
ok: true
|
|
48
77
|
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// The cookie Path to narrow the bypass to, or undefined to leave it site-wide
|
|
81
|
+
// (path missing — an older form — or unusable). Trailing slashes are stripped
|
|
82
|
+
// because cookie path-matching treats "/fonts/x" as covering both the page
|
|
83
|
+
// itself and everything under it, while "/fonts/x/" would miss the page.
|
|
84
|
+
function scopePath(path) {
|
|
85
|
+
if (typeof path !== 'string' || path.length > SCOPE_PATH_MAX_LENGTH || !SCOPE_PATH_RE.test(path)) {
|
|
86
|
+
return undefined;
|
|
87
|
+
}
|
|
88
|
+
const trimmed = path.replace(/\/+$/, '');
|
|
89
|
+
return trimmed === '' ? undefined : trimmed;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Replace the pending site-wide bypass cookie enable() just queued with one
|
|
93
|
+
// scoped to `path`. cookies() in a route handler exposes the same mutable
|
|
94
|
+
// store enable() wrote to, and setting the same cookie name again replaces the
|
|
95
|
+
// queued Set-Cookie rather than adding a second one — so exactly one bypass
|
|
96
|
+
// cookie leaves this response, and no Path=/ variant that could clobber an
|
|
97
|
+
// admin's site-wide preview bypass. The explicit attributes mirror enable()'s
|
|
98
|
+
// as a fallback; the spread keeps whatever enable() actually set (bar the
|
|
99
|
+
// path) if Next ever changes them.
|
|
100
|
+
async function scopeBypassCookie(path) {
|
|
101
|
+
if (!path) return;
|
|
102
|
+
const jar = await cookies();
|
|
103
|
+
const bypass = jar.get(PRERENDER_BYPASS_COOKIE);
|
|
104
|
+
if (!(bypass !== null && bypass !== void 0 && bypass.value)) return;
|
|
105
|
+
jar.set({
|
|
106
|
+
httpOnly: true,
|
|
107
|
+
sameSite: process.env.NODE_ENV !== 'development' ? 'none' : 'lax',
|
|
108
|
+
secure: process.env.NODE_ENV !== 'development',
|
|
109
|
+
...bypass,
|
|
110
|
+
path
|
|
111
|
+
});
|
|
49
112
|
}
|
|
@@ -8,7 +8,7 @@ import { NODE_ACCESS_HEADER } from '../nodeAccess.js';
|
|
|
8
8
|
// (defineVersionPlugin in .babelrc.cjs) with the literal package.json#version.
|
|
9
9
|
// Exported so UI (the admin toolbar) can surface it without re-reading the
|
|
10
10
|
// build-time global in a 'use client' module.
|
|
11
|
-
export const version = "3.2.
|
|
11
|
+
export const version = "3.2.2";
|
|
12
12
|
const IS_SERVER = typeof window === typeof undefined;
|
|
13
13
|
|
|
14
14
|
// Opt server fetches into Next's data cache only in production; dev stays
|
package/package.json
CHANGED
package/types/next-headers.d.ts
CHANGED
|
@@ -1,13 +1,33 @@
|
|
|
1
1
|
// Minimal declaration so src/next/tenant.ts type-checks without `next`
|
|
2
2
|
// installed (it's the host app's dependency; this package only ever runs the
|
|
3
3
|
// import inside a Next.js server).
|
|
4
|
+
//
|
|
5
|
+
// The cookie shape mirrors Next's route-handler cookie store: get() also
|
|
6
|
+
// carries the queued cookie's attributes (the store is ResponseCookies-backed,
|
|
7
|
+
// so a cookie draftMode().enable() just set comes back with its path/flags),
|
|
8
|
+
// and set() with an existing name replaces the queued Set-Cookie. The unlock
|
|
9
|
+
// route (src/next/unlock.ts) relies on both to narrow the bypass cookie's
|
|
10
|
+
// path.
|
|
4
11
|
declare module 'next/headers' {
|
|
12
|
+
interface CookieAttributes {
|
|
13
|
+
path?: string;
|
|
14
|
+
httpOnly?: boolean;
|
|
15
|
+
secure?: boolean;
|
|
16
|
+
sameSite?: 'strict' | 'lax' | 'none';
|
|
17
|
+
expires?: Date | number;
|
|
18
|
+
maxAge?: number;
|
|
19
|
+
domain?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
5
22
|
export function draftMode(): Promise<{
|
|
6
23
|
isEnabled: boolean;
|
|
7
24
|
enable(): void;
|
|
8
25
|
disable(): void;
|
|
9
26
|
}>;
|
|
10
27
|
export function cookies(): Promise<{
|
|
11
|
-
get(
|
|
28
|
+
get(
|
|
29
|
+
name: string,
|
|
30
|
+
): ({ name: string; value: string } & CookieAttributes) | undefined;
|
|
31
|
+
set(cookie: { name: string; value: string } & CookieAttributes): void;
|
|
12
32
|
}>;
|
|
13
33
|
}
|