fontdue-js 3.0.0-alpha9 → 3.0.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.
- package/CHANGELOG.md +14 -0
- package/README.md +182 -13
- package/dist/__generated__/CartOrderCompleteOrderMutation.graphql.d.ts +1 -1
- package/dist/__generated__/CartOrderCompleteOrderMutation.graphql.js +9 -3
- package/dist/__generated__/CartOrderRemoveDiscountMutation.graphql.d.ts +1 -1
- package/dist/__generated__/CartOrderRemoveDiscountMutation.graphql.js +9 -3
- package/dist/__generated__/CartOrderUpdateMutation.graphql.d.ts +1 -1
- package/dist/__generated__/CartOrderUpdateMutation.graphql.js +9 -3
- package/dist/__generated__/CartQuery.graphql.d.ts +1 -1
- package/dist/__generated__/CartQuery.graphql.js +9 -3
- package/dist/__generated__/CartStateUpdateMutation.graphql.d.ts +1 -1
- package/dist/__generated__/CartStateUpdateMutation.graphql.js +9 -3
- package/dist/__generated__/CharacterViewerIDQuery.graphql.d.ts +1 -1
- package/dist/__generated__/CharacterViewerIDQuery.graphql.js +9 -3
- package/dist/__generated__/CharacterViewerSlugQuery.graphql.d.ts +1 -1
- package/dist/__generated__/CharacterViewerSlugQuery.graphql.js +9 -3
- package/dist/__generated__/CharacterViewerStyleRefetchQuery.graphql.d.ts +1 -1
- package/dist/__generated__/CharacterViewerStyleRefetchQuery.graphql.js +9 -3
- package/dist/__generated__/CheckoutUpdateCustomerMutation.graphql.d.ts +1 -1
- package/dist/__generated__/CheckoutUpdateCustomerMutation.graphql.js +9 -3
- package/dist/__generated__/CheckoutUpdateOrderMutation.graphql.d.ts +1 -1
- package/dist/__generated__/CheckoutUpdateOrderMutation.graphql.js +9 -3
- package/dist/__generated__/CollectionAa_Query.graphql.d.ts +1 -1
- package/dist/__generated__/CollectionAa_Query.graphql.js +9 -3
- package/dist/__generated__/FontFamiliesQuery.graphql.d.ts +1 -1
- package/dist/__generated__/FontFamiliesQuery.graphql.js +9 -3
- package/dist/__generated__/FontdueAdminToolbarQuery.graphql.d.ts +20 -0
- package/dist/__generated__/FontdueAdminToolbarQuery.graphql.js +80 -0
- package/dist/__generated__/FontdueAdminToolbarTokenMutation.graphql.d.ts +18 -0
- package/dist/__generated__/FontdueAdminToolbarTokenMutation.graphql.js +56 -0
- package/dist/__generated__/PrecartAddToCartMutation.graphql.d.ts +1 -1
- package/dist/__generated__/PrecartAddToCartMutation.graphql.js +9 -3
- package/dist/__generated__/StoreModalCartQuery.graphql.d.ts +1 -1
- package/dist/__generated__/StoreModalCartQuery.graphql.js +9 -3
- package/dist/__generated__/StoreModalContainerQuery.graphql.d.ts +1 -1
- package/dist/__generated__/StoreModalContainerQuery.graphql.js +9 -3
- package/dist/__generated__/StoreModalIndexQuery.graphql.d.ts +1 -1
- package/dist/__generated__/StoreModalIndexQuery.graphql.js +9 -3
- package/dist/__generated__/StoreModalProductQuery.graphql.d.ts +1 -1
- package/dist/__generated__/StoreModalProductQuery.graphql.js +9 -3
- package/dist/__generated__/StoreModalProductRefetchQuery.graphql.d.ts +1 -1
- package/dist/__generated__/StoreModalProductRefetchQuery.graphql.js +9 -3
- package/dist/__generated__/TestFontsFormUpdateCustomerMutation.graphql.d.ts +1 -1
- package/dist/__generated__/TestFontsFormUpdateCustomerMutation.graphql.js +9 -3
- package/dist/__generated__/TypeTesterStandaloneChangedStylesQuery.graphql.d.ts +1 -1
- package/dist/__generated__/TypeTesterStandaloneChangedStylesQuery.graphql.js +9 -3
- package/dist/__generated__/TypeTesterStandaloneQuery.graphql.d.ts +1 -1
- package/dist/__generated__/TypeTesterStandaloneQuery.graphql.js +9 -3
- package/dist/__generated__/TypeTestersChangedStylesQuery.graphql.d.ts +1 -1
- package/dist/__generated__/TypeTestersChangedStylesQuery.graphql.js +9 -3
- package/dist/__generated__/TypeTestersIDQuery.graphql.d.ts +1 -1
- package/dist/__generated__/TypeTestersIDQuery.graphql.js +9 -3
- package/dist/__generated__/TypeTestersRefetchQuery.graphql.d.ts +1 -1
- package/dist/__generated__/TypeTestersRefetchQuery.graphql.js +9 -3
- package/dist/__generated__/TypeTestersSlugQuery.graphql.d.ts +1 -1
- package/dist/__generated__/TypeTestersSlugQuery.graphql.js +9 -3
- package/dist/__generated__/useFontStyle_fontStyle.graphql.d.ts +2 -1
- package/dist/__generated__/useFontStyle_fontStyle.graphql.js +8 -2
- package/dist/__tests__/createFontdueFetch.test.js +276 -0
- package/dist/__tests__/imageLoader.test.js +62 -0
- package/dist/__tests__/metricFallback.test.js +74 -0
- package/dist/__tests__/networkFetch.test.js +125 -3
- package/dist/__tests__/nextAdapter.test.js +175 -60
- package/dist/__tests__/preview.test.js +217 -0
- package/dist/__tests__/previewServer.test.js +118 -0
- package/dist/__tests__/previewState.test.js +63 -0
- package/dist/__tests__/serverConfig.test.js +62 -0
- package/dist/components/BuyButton/index.d.ts +2 -2
- package/dist/components/BuyButton/index.js +3 -3
- package/dist/components/CharacterViewer/index.d.ts +2 -2
- package/dist/components/CharacterViewer/index.js +20 -11
- package/dist/components/ConfigContext.d.ts +21 -2
- package/dist/components/ConfigContext.js +12 -2
- package/dist/components/ConnectionErrorToolbar.d.ts +1 -0
- package/dist/components/ConnectionErrorToolbar.js +106 -0
- package/dist/components/FontdueAdminToolbar/index.d.ts +2 -0
- package/dist/components/FontdueAdminToolbar/index.js +299 -0
- package/dist/components/FontdueAdminToolbar/previewState.d.ts +7 -0
- package/dist/components/FontdueAdminToolbar/previewState.js +58 -0
- package/dist/components/FontdueContextProvider/index.js +4 -2
- package/dist/components/FontdueProvider/index.js +6 -1
- package/dist/components/FontdueProvider/index.server.d.ts +1 -0
- package/dist/components/FontdueProvider/index.server.js +10 -0
- package/dist/components/NewsletterSignup/index.d.ts +2 -2
- package/dist/components/NewsletterSignup/index.js +2 -2
- package/dist/components/Root/index.js +16 -2
- package/dist/components/TestFontsForm/index.d.ts +2 -2
- package/dist/components/TestFontsForm/index.js +2 -2
- package/dist/components/TypeTester/TypeTesterStandalone.d.ts +2 -2
- package/dist/components/TypeTester/TypeTesterStandalone.js +2 -2
- package/dist/components/TypeTesters/index.d.ts +2 -2
- package/dist/components/TypeTesters/index.js +3 -3
- package/dist/components/useFontStyle.d.ts +1 -0
- package/dist/components/useFontStyle.js +12 -3
- package/dist/corsError.d.ts +1 -5
- package/dist/corsError.js +23 -13
- package/dist/data/unicodeNamesUrl.d.ts +2 -0
- package/dist/data/unicodeNamesUrl.js +18 -0
- package/dist/data/unicodeNamesVersion.d.ts +1 -0
- package/dist/data/unicodeNamesVersion.js +4 -0
- package/dist/fallbackFontData.d.ts +2 -0
- package/dist/fallbackFontData.js +10 -0
- package/dist/fontdue.css +231 -4
- package/dist/loadFontdueProviderQuery.d.ts +2 -1
- package/dist/loadFontdueProviderQuery.js +5 -2
- package/dist/metricFallback.d.ts +48 -0
- package/dist/metricFallback.js +98 -0
- package/dist/next/image-loader.js +22 -3
- package/dist/next/index.d.ts +1 -2
- package/dist/next/index.js +14 -6
- package/dist/next/registerSingleTenantResolver.d.ts +1 -0
- package/dist/next/registerSingleTenantResolver.js +35 -0
- package/dist/next/revalidate.js +1 -1
- package/dist/next/tenant.d.ts +4 -4
- package/dist/next/tenant.js +89 -58
- package/dist/preview/constants.d.ts +9 -0
- package/dist/preview/constants.js +117 -0
- package/dist/preview/index.d.ts +53 -0
- package/dist/preview/index.js +190 -0
- package/dist/preview/server.d.ts +20 -0
- package/dist/preview/server.js +89 -0
- package/dist/relay/environment.d.ts +8 -0
- package/dist/relay/environment.js +81 -35
- package/dist/relay/loadSerializableQuery.d.ts +13 -3
- package/dist/relay/loadSerializableQuery.js +2 -0
- package/dist/relay/serverConfig.d.ts +5 -7
- package/dist/relay/serverConfig.js +83 -8
- package/dist/scripts/publishUnicodeData.js +68 -0
- package/dist/scripts/updateUnicodeData.js +41 -6
- package/dist/server/index.d.ts +37 -0
- package/dist/server/index.js +160 -0
- package/package.json +5 -1
- package/types/next-headers.d.ts +9 -0
- package/types/next-navigation.d.ts +4 -0
- package/vitest.config.ts +5 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { PREVIEW_TOKEN_COOKIE, PREVIEW_MARKER_COOKIE, PREVIEW_ENDPOINT, DEFAULT_PREVIEW_TTL_MS, PREVIEW_MARKER_GRACE_MS } from './constants.js';
|
|
2
|
+
export { PREVIEW_TOKEN_COOKIE, PREVIEW_MARKER_COOKIE, PREVIEW_ENDPOINT, DEFAULT_PREVIEW_TTL_MS, PREVIEW_MARKER_GRACE_MS, };
|
|
3
|
+
export interface PreviewCookieOptions {
|
|
4
|
+
/**
|
|
5
|
+
* Whether to set the cookies' Secure attribute. Defaults to true on https
|
|
6
|
+
* requests and false otherwise, so cookies work over http in local dev.
|
|
7
|
+
*/
|
|
8
|
+
secure?: boolean;
|
|
9
|
+
/** SameSite attribute. Defaults to 'lax' — the toolbar is same-site. */
|
|
10
|
+
sameSite?: 'lax' | 'strict' | 'none';
|
|
11
|
+
/** Cookie path. Defaults to '/'. */
|
|
12
|
+
path?: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Web-standard route handler implementing the portable preview contract. Wire
|
|
16
|
+
* it into your framework's route at the path the toolbar targets
|
|
17
|
+
* (`/api/preview` by default):
|
|
18
|
+
*
|
|
19
|
+
* // Astro: src/pages/api/preview.ts
|
|
20
|
+
* import { handlePreviewRequest } from 'fontdue-js/preview';
|
|
21
|
+
* export const ALL: APIRoute = ({ request }) => handlePreviewRequest(request);
|
|
22
|
+
*
|
|
23
|
+
* // React Router 7: app/routes/api.preview.ts
|
|
24
|
+
* import { handlePreviewRequest } from 'fontdue-js/preview';
|
|
25
|
+
* export const action = ({ request }: Route.ActionArgs) =>
|
|
26
|
+
* handlePreviewRequest(request);
|
|
27
|
+
*
|
|
28
|
+
* POST expects a JSON body `{ token: string, expiresAt?: string | number }`;
|
|
29
|
+
* DELETE takes no body. `expiresAt` (ISO-8601 or epoch-ms) is the token's
|
|
30
|
+
* expiry; it's stored in the readable marker cookie so the toolbar can warn
|
|
31
|
+
* when preview has lapsed, and defaults to now + DEFAULT_PREVIEW_TTL_MS when
|
|
32
|
+
* omitted. The token is opaque here — the Fontdue GraphQL server validates it
|
|
33
|
+
* on each request, so a forged token simply reveals nothing. Frameworks that
|
|
34
|
+
* cache HTML should also bypass their shared/CDN cache when
|
|
35
|
+
* PREVIEW_MARKER_COOKIE is present, so an admin preview is never served to the
|
|
36
|
+
* public.
|
|
37
|
+
*/
|
|
38
|
+
export declare function handlePreviewRequest(request: Request, options?: PreviewCookieOptions): Promise<Response>;
|
|
39
|
+
/**
|
|
40
|
+
* Extract the preview token from a Cookie header string, e.g.
|
|
41
|
+
* `readPreviewToken(request.headers.get('cookie'))`. This is the canonical
|
|
42
|
+
* reader — it reverses the encoding `handlePreviewRequest` applies, so the
|
|
43
|
+
* token comes back verbatim. Returns undefined when not in preview.
|
|
44
|
+
*/
|
|
45
|
+
export declare function readPreviewToken(cookieHeader: string | null | undefined): string | undefined;
|
|
46
|
+
/**
|
|
47
|
+
* Headers to merge into a server-side GraphQL fetch so the request reveals
|
|
48
|
+
* hidden fonts. Spread the result into your app's fetch headers, into
|
|
49
|
+
* fontdue-js's per-render `serverConfig.headers`, or into the `headers` option
|
|
50
|
+
* of a preload helper (loadFontdueProviderQuery, loadTypeTesterQuery, …).
|
|
51
|
+
* Returns {} when there is no token, so it's safe to spread unconditionally.
|
|
52
|
+
*/
|
|
53
|
+
export declare function previewAuthHeaders(token: string | null | undefined): Record<string, string>;
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
// Portable admin-preview contract, shared by every framework adapter.
|
|
2
|
+
//
|
|
3
|
+
// The admin toolbar (FontdueAdminToolbar, auto-mounted by FontdueProvider)
|
|
4
|
+
// detects a logged-in admin session, brokers a short-lived admin token, and
|
|
5
|
+
// POSTs it to a small preview route on the storefront's own origin to "enter
|
|
6
|
+
// preview"; a DELETE exits. In Next.js that route additionally toggles draft
|
|
7
|
+
// mode, but the underlying contract is framework-agnostic:
|
|
8
|
+
//
|
|
9
|
+
// - Enter (POST { token }): set an httpOnly token cookie + a readable marker
|
|
10
|
+
// cookie, so server renders forward the token and the client toolbar can
|
|
11
|
+
// tell preview is on.
|
|
12
|
+
// - Exit (DELETE): clear both cookies.
|
|
13
|
+
// - Server fetch: when the token cookie is present, forward it as
|
|
14
|
+
// `Authorization: Bearer <token>` so GraphQL reveals hidden (unpublished)
|
|
15
|
+
// fonts. The public never has these cookies, so their renders stay
|
|
16
|
+
// sessionless and cacheable.
|
|
17
|
+
//
|
|
18
|
+
// Astro, React Router 7 and TanStack Start all speak the Web Fetch API, so
|
|
19
|
+
// `handlePreviewRequest` is a drop-in route handler for all of them. Next keeps
|
|
20
|
+
// its own route because it layers Next draft mode on top of the same cookies.
|
|
21
|
+
//
|
|
22
|
+
// Why not route this through fontdue-js's per-render `setFontdueServerConfig`
|
|
23
|
+
// store? That store is React.cache-backed and only isolates per request inside
|
|
24
|
+
// an RSC render (Next). Outside RSC — Astro frontmatter, RR7 loaders — writes
|
|
25
|
+
// are no-ops (see relay/serverConfig.ts). Two portable ways to get the token to
|
|
26
|
+
// server fetches instead:
|
|
27
|
+
//
|
|
28
|
+
// - Ambient (recommended): wrap requests in `runWithPreview` from
|
|
29
|
+
// fontdue-js/preview/server, which holds the token in AsyncLocalStorage for
|
|
30
|
+
// the request so every fontdue-js fetch/preload forwards it with no
|
|
31
|
+
// per-call plumbing — and forces preview responses out of shared caches.
|
|
32
|
+
// - Explicit: read the cookie per request and pass `previewAuthHeaders(token)`
|
|
33
|
+
// via the `headers` option on the app's own GraphQL fetch and on each
|
|
34
|
+
// preload helper (loadFontdueProviderQuery, loadTypeTesterQuery, …). This
|
|
35
|
+
// mirrors how headless-CMS previews (e.g. Sanity's loadQuery) pass
|
|
36
|
+
// per-request state explicitly, and is the fallback where ambient context
|
|
37
|
+
// can't propagate.
|
|
38
|
+
|
|
39
|
+
import { PREVIEW_TOKEN_COOKIE, PREVIEW_MARKER_COOKIE, PREVIEW_ENDPOINT, DEFAULT_PREVIEW_TTL_MS, PREVIEW_MARKER_GRACE_MS } from './constants.js';
|
|
40
|
+
export { PREVIEW_TOKEN_COOKIE, PREVIEW_MARKER_COOKIE, PREVIEW_ENDPOINT, DEFAULT_PREVIEW_TTL_MS, PREVIEW_MARKER_GRACE_MS };
|
|
41
|
+
function serializeCookie(name, value, opts) {
|
|
42
|
+
const sameSite = opts.sameSite === 'strict' ? 'Strict' : opts.sameSite === 'none' ? 'None' : 'Lax';
|
|
43
|
+
const parts = [`${name}=${encodeURIComponent(value)}`, `Path=${opts.path ?? '/'}`, `SameSite=${sameSite}`];
|
|
44
|
+
if (opts.httpOnly) parts.push('HttpOnly');
|
|
45
|
+
if (opts.secure) parts.push('Secure');
|
|
46
|
+
if (opts.maxAge != null) parts.push(`Max-Age=${opts.maxAge}`);
|
|
47
|
+
return parts.join('; ');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Coerce an `expiresAt` from the POST body — an ISO-8601 string, an epoch-ms
|
|
51
|
+
// number, or a numeric string — into epoch-ms. Falls back to
|
|
52
|
+
// now + DEFAULT_PREVIEW_TTL_MS when absent or unparseable, since the canonical
|
|
53
|
+
// 1h TTL lives in the backend token and the field may not be present yet (the
|
|
54
|
+
// backend half ships separately). Never throws.
|
|
55
|
+
function resolveExpiresAt(value) {
|
|
56
|
+
if (typeof value === 'number' && Number.isFinite(value)) return value;
|
|
57
|
+
if (typeof value === 'string' && value !== '') {
|
|
58
|
+
const asNumber = Number(value);
|
|
59
|
+
if (Number.isFinite(asNumber)) return asNumber;
|
|
60
|
+
const asDate = Date.parse(value);
|
|
61
|
+
if (!Number.isNaN(asDate)) return asDate;
|
|
62
|
+
}
|
|
63
|
+
return Date.now() + DEFAULT_PREVIEW_TTL_MS;
|
|
64
|
+
}
|
|
65
|
+
function isSecureRequest(request) {
|
|
66
|
+
try {
|
|
67
|
+
return new URL(request.url).protocol === 'https:';
|
|
68
|
+
} catch {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function jsonResponse(body, status) {
|
|
73
|
+
let setCookies = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : [];
|
|
74
|
+
const headers = new Headers({
|
|
75
|
+
'content-type': 'application/json'
|
|
76
|
+
});
|
|
77
|
+
for (const cookie of setCookies) headers.append('Set-Cookie', cookie);
|
|
78
|
+
return new Response(JSON.stringify(body), {
|
|
79
|
+
status,
|
|
80
|
+
headers
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Web-standard route handler implementing the portable preview contract. Wire
|
|
86
|
+
* it into your framework's route at the path the toolbar targets
|
|
87
|
+
* (`/api/preview` by default):
|
|
88
|
+
*
|
|
89
|
+
* // Astro: src/pages/api/preview.ts
|
|
90
|
+
* import { handlePreviewRequest } from 'fontdue-js/preview';
|
|
91
|
+
* export const ALL: APIRoute = ({ request }) => handlePreviewRequest(request);
|
|
92
|
+
*
|
|
93
|
+
* // React Router 7: app/routes/api.preview.ts
|
|
94
|
+
* import { handlePreviewRequest } from 'fontdue-js/preview';
|
|
95
|
+
* export const action = ({ request }: Route.ActionArgs) =>
|
|
96
|
+
* handlePreviewRequest(request);
|
|
97
|
+
*
|
|
98
|
+
* POST expects a JSON body `{ token: string, expiresAt?: string | number }`;
|
|
99
|
+
* DELETE takes no body. `expiresAt` (ISO-8601 or epoch-ms) is the token's
|
|
100
|
+
* expiry; it's stored in the readable marker cookie so the toolbar can warn
|
|
101
|
+
* when preview has lapsed, and defaults to now + DEFAULT_PREVIEW_TTL_MS when
|
|
102
|
+
* omitted. The token is opaque here — the Fontdue GraphQL server validates it
|
|
103
|
+
* on each request, so a forged token simply reveals nothing. Frameworks that
|
|
104
|
+
* cache HTML should also bypass their shared/CDN cache when
|
|
105
|
+
* PREVIEW_MARKER_COOKIE is present, so an admin preview is never served to the
|
|
106
|
+
* public.
|
|
107
|
+
*/
|
|
108
|
+
export async function handlePreviewRequest(request) {
|
|
109
|
+
let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
110
|
+
const base = {
|
|
111
|
+
secure: options.secure ?? isSecureRequest(request),
|
|
112
|
+
sameSite: options.sameSite ?? 'lax',
|
|
113
|
+
path: options.path ?? '/'
|
|
114
|
+
};
|
|
115
|
+
if (request.method === 'DELETE') {
|
|
116
|
+
return jsonResponse({
|
|
117
|
+
ok: true,
|
|
118
|
+
preview: false
|
|
119
|
+
}, 200, [serializeCookie(PREVIEW_TOKEN_COOKIE, '', {
|
|
120
|
+
...base,
|
|
121
|
+
httpOnly: true,
|
|
122
|
+
maxAge: 0
|
|
123
|
+
}), serializeCookie(PREVIEW_MARKER_COOKIE, '', {
|
|
124
|
+
...base,
|
|
125
|
+
httpOnly: false,
|
|
126
|
+
maxAge: 0
|
|
127
|
+
})]);
|
|
128
|
+
}
|
|
129
|
+
if (request.method === 'POST') {
|
|
130
|
+
const body = await request.json().catch(() => ({}));
|
|
131
|
+
const token = body.token;
|
|
132
|
+
if (typeof token !== 'string' || token === '') {
|
|
133
|
+
return jsonResponse({
|
|
134
|
+
ok: false,
|
|
135
|
+
error: 'Missing preview token'
|
|
136
|
+
}, 400);
|
|
137
|
+
}
|
|
138
|
+
const expiresAt = resolveExpiresAt(body.expiresAt);
|
|
139
|
+
// Marker outlives the token by a grace window so the toolbar can reach the
|
|
140
|
+
// "expired" warning state instead of the cookie silently vanishing.
|
|
141
|
+
const markerMaxAge = Math.max(0, Math.ceil((expiresAt - Date.now() + PREVIEW_MARKER_GRACE_MS) / 1000));
|
|
142
|
+
return jsonResponse({
|
|
143
|
+
ok: true,
|
|
144
|
+
preview: true,
|
|
145
|
+
expiresAt
|
|
146
|
+
}, 200, [serializeCookie(PREVIEW_TOKEN_COOKIE, token, {
|
|
147
|
+
...base,
|
|
148
|
+
httpOnly: true
|
|
149
|
+
}), serializeCookie(PREVIEW_MARKER_COOKIE, String(expiresAt), {
|
|
150
|
+
...base,
|
|
151
|
+
httpOnly: false,
|
|
152
|
+
maxAge: markerMaxAge
|
|
153
|
+
})]);
|
|
154
|
+
}
|
|
155
|
+
return jsonResponse({
|
|
156
|
+
ok: false,
|
|
157
|
+
error: 'Method not allowed'
|
|
158
|
+
}, 405);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Extract the preview token from a Cookie header string, e.g.
|
|
163
|
+
* `readPreviewToken(request.headers.get('cookie'))`. This is the canonical
|
|
164
|
+
* reader — it reverses the encoding `handlePreviewRequest` applies, so the
|
|
165
|
+
* token comes back verbatim. Returns undefined when not in preview.
|
|
166
|
+
*/
|
|
167
|
+
export function readPreviewToken(cookieHeader) {
|
|
168
|
+
if (!cookieHeader) return undefined;
|
|
169
|
+
for (const part of cookieHeader.split(';')) {
|
|
170
|
+
const idx = part.indexOf('=');
|
|
171
|
+
if (idx === -1) continue;
|
|
172
|
+
if (part.slice(0, idx).trim() !== PREVIEW_TOKEN_COOKIE) continue;
|
|
173
|
+
const value = part.slice(idx + 1).trim();
|
|
174
|
+
return value === '' ? undefined : decodeURIComponent(value);
|
|
175
|
+
}
|
|
176
|
+
return undefined;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Headers to merge into a server-side GraphQL fetch so the request reveals
|
|
181
|
+
* hidden fonts. Spread the result into your app's fetch headers, into
|
|
182
|
+
* fontdue-js's per-render `serverConfig.headers`, or into the `headers` option
|
|
183
|
+
* of a preload helper (loadFontdueProviderQuery, loadTypeTesterQuery, …).
|
|
184
|
+
* Returns {} when there is no token, so it's safe to spread unconditionally.
|
|
185
|
+
*/
|
|
186
|
+
export function previewAuthHeaders(token) {
|
|
187
|
+
return token ? {
|
|
188
|
+
authorization: `Bearer ${token}`
|
|
189
|
+
} : {};
|
|
190
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** Whether the current async context is an admin preview. */
|
|
2
|
+
export declare function isPreviewing(): boolean;
|
|
3
|
+
/**
|
|
4
|
+
* Preview auth headers for the current async context, or {} when not previewing.
|
|
5
|
+
* Spread into a hand-rolled fetch you make inside `runWithPreview`; the
|
|
6
|
+
* fontdue-js fetch/preload helpers pick these up automatically and don't need
|
|
7
|
+
* it.
|
|
8
|
+
*/
|
|
9
|
+
export declare function ambientPreviewHeaders(): Record<string, string>;
|
|
10
|
+
/**
|
|
11
|
+
* Run `next` (your framework's render/loader chain) with the request's preview
|
|
12
|
+
* token in ambient context, then return its Response. When the request is
|
|
13
|
+
* previewing, the response's cache headers are rewritten so it is never stored
|
|
14
|
+
* in a shared/CDN cache. Public (no-token) requests pass through untouched and
|
|
15
|
+
* stay fully cacheable.
|
|
16
|
+
*
|
|
17
|
+
* `next` is your middleware's continuation — `next` in Astro, `next` in a React
|
|
18
|
+
* Router 7 middleware. It must return (a promise of) the Response.
|
|
19
|
+
*/
|
|
20
|
+
export declare function runWithPreview(request: Request, next: () => Response | Promise<Response>): Promise<Response>;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// Ambient (request-scoped) preview for non-RSC server frameworks.
|
|
2
|
+
//
|
|
3
|
+
// fontdue-js/preview gives you the cookie contract and an explicit option:
|
|
4
|
+
// read the token per request and thread `previewAuthHeaders(token)` into every
|
|
5
|
+
// GraphQL fetch and preload helper. That's portable but easy to get wrong — miss
|
|
6
|
+
// one preload and that island silently renders the public view.
|
|
7
|
+
//
|
|
8
|
+
// This module removes the threading. `runWithPreview` establishes an
|
|
9
|
+
// AsyncLocalStorage context for the duration of a request; while it's active,
|
|
10
|
+
// every fontdue-js server fetch (createFontdueFetch and all the preload helpers)
|
|
11
|
+
// automatically forwards the admin preview token, with no per-call plumbing —
|
|
12
|
+
// the same ergonomics RSC gets from React.cache, and the same mechanism Next's
|
|
13
|
+
// own headers()/draftMode() use under the hood. Install it once in your
|
|
14
|
+
// framework's middleware:
|
|
15
|
+
//
|
|
16
|
+
// // Astro: src/middleware.ts
|
|
17
|
+
// export const onRequest = (ctx, next) => runWithPreview(ctx.request, next);
|
|
18
|
+
//
|
|
19
|
+
// // React Router 7 (root route, with future.v8_middleware):
|
|
20
|
+
// export const middleware = [({ request }, next) => runWithPreview(request, next)];
|
|
21
|
+
//
|
|
22
|
+
// runWithPreview also binds the cache guarantee to the same call: when a request
|
|
23
|
+
// is previewing, the response is forced out of any shared/CDN cache. Because the
|
|
24
|
+
// context and the no-cache rule are set in one place, you can't be ambiently
|
|
25
|
+
// previewing without the response being uncacheable — so an admin render is never
|
|
26
|
+
// served to the public.
|
|
27
|
+
//
|
|
28
|
+
// Requirements & fallback: AsyncLocalStorage must propagate from the install
|
|
29
|
+
// point into the render. That holds for in-process middleware — Node (Netlify
|
|
30
|
+
// Functions, the default SSR target), Deno (Netlify Edge), Bun. It does NOT hold
|
|
31
|
+
// when middleware runs in a separate runtime from the render (e.g. Astro with
|
|
32
|
+
// `edgeMiddleware: true`, where locals cross the boundary as a serialized
|
|
33
|
+
// header). In that case fall back to the explicit path: read the token in
|
|
34
|
+
// middleware and thread `previewAuthHeaders(token)` via the `headers` option on
|
|
35
|
+
// fetches/preloads. The explicit `headers` option always overrides the ambient
|
|
36
|
+
// context, so the two compose.
|
|
37
|
+
|
|
38
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
39
|
+
import { readPreviewToken, previewAuthHeaders } from './index.js';
|
|
40
|
+
import { registerAmbientConfigResolver } from '../relay/serverConfig.js';
|
|
41
|
+
const previewStore = new AsyncLocalStorage();
|
|
42
|
+
|
|
43
|
+
// Feed the ambient token into fontdue-js's server config resolution. Returns
|
|
44
|
+
// undefined outside a preview context, so non-preview requests are untouched.
|
|
45
|
+
registerAmbientConfigResolver(() => {
|
|
46
|
+
const store = previewStore.getStore();
|
|
47
|
+
return store ? {
|
|
48
|
+
headers: store.headers
|
|
49
|
+
} : undefined;
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
/** Whether the current async context is an admin preview. */
|
|
53
|
+
export function isPreviewing() {
|
|
54
|
+
return previewStore.getStore() != null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Preview auth headers for the current async context, or {} when not previewing.
|
|
59
|
+
* Spread into a hand-rolled fetch you make inside `runWithPreview`; the
|
|
60
|
+
* fontdue-js fetch/preload helpers pick these up automatically and don't need
|
|
61
|
+
* it.
|
|
62
|
+
*/
|
|
63
|
+
export function ambientPreviewHeaders() {
|
|
64
|
+
var _previewStore$getStor;
|
|
65
|
+
return ((_previewStore$getStor = previewStore.getStore()) === null || _previewStore$getStor === void 0 ? void 0 : _previewStore$getStor.headers) ?? {};
|
|
66
|
+
}
|
|
67
|
+
const NO_STORE = 'private, no-store';
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Run `next` (your framework's render/loader chain) with the request's preview
|
|
71
|
+
* token in ambient context, then return its Response. When the request is
|
|
72
|
+
* previewing, the response's cache headers are rewritten so it is never stored
|
|
73
|
+
* in a shared/CDN cache. Public (no-token) requests pass through untouched and
|
|
74
|
+
* stay fully cacheable.
|
|
75
|
+
*
|
|
76
|
+
* `next` is your middleware's continuation — `next` in Astro, `next` in a React
|
|
77
|
+
* Router 7 middleware. It must return (a promise of) the Response.
|
|
78
|
+
*/
|
|
79
|
+
export async function runWithPreview(request, next) {
|
|
80
|
+
const token = readPreviewToken(request.headers.get('cookie'));
|
|
81
|
+
if (!token) return next();
|
|
82
|
+
const response = await previewStore.run({
|
|
83
|
+
headers: previewAuthHeaders(token)
|
|
84
|
+
}, next);
|
|
85
|
+
response.headers.set('Cache-Control', NO_STORE);
|
|
86
|
+
response.headers.delete('Netlify-CDN-Cache-Control');
|
|
87
|
+
response.headers.delete('CDN-Cache-Control');
|
|
88
|
+
return response;
|
|
89
|
+
}
|
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
import { Environment, RequestParameters, QueryResponseCache, Variables, GraphQLResponse } from 'relay-runtime';
|
|
2
|
+
export declare const version: string;
|
|
3
|
+
export declare function fontdueBaseUrl(): string | undefined;
|
|
2
4
|
export declare function createNetworkFetch(options?: CreateRelayEnvironmentOptions): (request: RequestParameters, variables: Variables) => Promise<GraphQLResponse>;
|
|
3
5
|
export declare const networkFetch: (request: RequestParameters, variables: Variables) => Promise<GraphQLResponse>;
|
|
4
6
|
export declare const responseCache: QueryResponseCache | null;
|
|
5
7
|
interface CreateRelayEnvironmentOptions {
|
|
6
8
|
url?: string;
|
|
9
|
+
/**
|
|
10
|
+
* Extra headers for this fetch (server-side). Used to forward a per-request
|
|
11
|
+
* preview `Authorization: Bearer <token>` in non-RSC frameworks, where the
|
|
12
|
+
* render-scoped serverConfig store is a no-op. See ../preview.
|
|
13
|
+
*/
|
|
14
|
+
headers?: Record<string, string>;
|
|
7
15
|
stripeIntegration?: 'card-element' | 'dynamic';
|
|
8
16
|
}
|
|
9
17
|
export declare function createEnvironment(options: CreateRelayEnvironmentOptions): Environment;
|
|
@@ -1,12 +1,24 @@
|
|
|
1
1
|
import { Environment, Network, RecordSource, Store, QueryResponseCache } from 'relay-runtime';
|
|
2
2
|
import { handlePossibleCorsError } from '../corsError.js';
|
|
3
|
-
import {
|
|
3
|
+
import { resolveFontdueServerConfig } from './serverConfig.js';
|
|
4
|
+
import { PREVIEW_HEADER, hasPreviewMarkerCookie } from '../preview/constants.js';
|
|
4
5
|
|
|
5
6
|
// `__FONTDUE_JS_VERSION__` is replaced by an inline babel plugin
|
|
6
7
|
// (defineVersionPlugin in .babelrc.cjs) with the literal package.json#version.
|
|
7
|
-
|
|
8
|
+
// Exported so UI (the admin toolbar) can surface it without re-reading the
|
|
9
|
+
// build-time global in a 'use client' module.
|
|
10
|
+
export const version = "3.0.0";
|
|
8
11
|
const IS_SERVER = typeof window === typeof undefined;
|
|
9
12
|
|
|
13
|
+
// Opt server fetches into Next's data cache only in production; dev stays
|
|
14
|
+
// uncached so storefront content is always fresh (see the init.cache note
|
|
15
|
+
// below). Read per call (not memoized) so the literal `process.env.NODE_ENV` is
|
|
16
|
+
// inlined at build time and tests can stub it; `typeof process` guards non-Node
|
|
17
|
+
// bundles from throwing.
|
|
18
|
+
function cacheInProduction() {
|
|
19
|
+
return typeof process !== 'undefined' && process.env.NODE_ENV === 'production';
|
|
20
|
+
}
|
|
21
|
+
|
|
10
22
|
// Read env from either process.env (Node/Next.js) or import.meta.env (Vite/Astro).
|
|
11
23
|
// Prefer the framework-agnostic FONTDUE_URL name; fall back to NEXT_PUBLIC_FONTDUE_URL
|
|
12
24
|
// for backwards compatibility with existing Next.js integrations.
|
|
@@ -36,48 +48,82 @@ const FONTDUE_URL = readEnv('FONTDUE_URL') ?? readEnv('NEXT_PUBLIC_FONTDUE_URL')
|
|
|
36
48
|
const STRIPE_INTEGRATION = readEnv('FONTDUE_STRIPE_INTEGRATION') ?? readEnv('NEXT_PUBLIC_FONTDUE_STRIPE_INTEGRATION') ?? NEXT_PUBLIC_STRIPE;
|
|
37
49
|
const CACHE_TTL = 10 * 1000; // 10 seconds, to resolve preloaded results
|
|
38
50
|
|
|
51
|
+
// The configured Fontdue base URL resolved for the current runtime, or
|
|
52
|
+
// undefined when it can't be determined (e.g. multi-tenant, where fetches go to
|
|
53
|
+
// the page's own origin). The admin toolbar uses it to show where the admin is
|
|
54
|
+
// signed in and to link back to the Fontdue admin.
|
|
55
|
+
export function fontdueBaseUrl() {
|
|
56
|
+
return FONTDUE_URL || undefined;
|
|
57
|
+
}
|
|
39
58
|
export function createNetworkFetch(options) {
|
|
40
59
|
return async function networkFetch(request, variables) {
|
|
41
|
-
// Per-render server config (set via setFontdueServerConfig
|
|
42
|
-
// FontdueProvider server entrypoint
|
|
43
|
-
//
|
|
44
|
-
|
|
60
|
+
// Per-render server config (the RSC slot set via setFontdueServerConfig /
|
|
61
|
+
// the FontdueProvider server entrypoint, the runWithPreview AsyncLocalStorage
|
|
62
|
+
// store, or the Next single-tenant ambient resolver). Awaited per call, not
|
|
63
|
+
// per createNetworkFetch, because module-level fetchers outlive renders — so
|
|
64
|
+
// an embedded component's own preload reveals hidden fonts in preview even
|
|
65
|
+
// on a page that never calls the app's GraphQL fetcher.
|
|
66
|
+
const serverConfig = IS_SERVER ? await resolveFontdueServerConfig() : undefined;
|
|
45
67
|
const base = (options === null || options === void 0 ? void 0 : options.url) ?? (serverConfig === null || serverConfig === void 0 ? void 0 : serverConfig.url) ?? FONTDUE_URL;
|
|
46
68
|
if (IS_SERVER && (base == null || base === '')) {
|
|
47
69
|
throw new Error('fontdue-js: no Fontdue URL configured for server-side fetch. ' + 'Set FONTDUE_URL / PUBLIC_FONTDUE_URL / VITE_FONTDUE_URL in your environment, ' + 'pass `url` to loadSerializableQuery, or call setFontdueServerConfig ' + '(or render <FontdueProvider url=…>) earlier in the server render.');
|
|
48
70
|
}
|
|
49
71
|
const url = `${base ?? ''}/graphql`;
|
|
72
|
+
const headers = {
|
|
73
|
+
...(serverConfig === null || serverConfig === void 0 ? void 0 : serverConfig.headers),
|
|
74
|
+
// Per-call headers (e.g. a preview Authorization: Bearer token passed to
|
|
75
|
+
// a preload helper) override the render-store headers.
|
|
76
|
+
...(options === null || options === void 0 ? void 0 : options.headers),
|
|
77
|
+
Accept: 'application/json',
|
|
78
|
+
'Content-Type': 'application/json',
|
|
79
|
+
'fontdue-stripe-integration': (options === null || options === void 0 ? void 0 : options.stripeIntegration) ?? STRIPE_INTEGRATION ?? 'dynamic',
|
|
80
|
+
'fontdue-client-version': version
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
// Whether this request is an admin *preview*. On the server that's a
|
|
84
|
+
// forwarded admin token; in the browser it's the readable preview marker
|
|
85
|
+
// cookie — NOT a bare admin session cookie that happens to ride the fetch
|
|
86
|
+
// (that conflation was the leak: a logged-in admin browsing normally would
|
|
87
|
+
// see hidden fonts). It's sent explicitly as the `fontdue-preview` header so
|
|
88
|
+
// the GraphQL server reveals hidden (unpublished) fonts only in preview:
|
|
89
|
+
// "false" forces the public view even for a logged-in admin, and an absent
|
|
90
|
+
// header (older clients) keeps the legacy "any admin sees hidden" behavior.
|
|
91
|
+
const previewing = IS_SERVER ? headers.authorization != null || headers.Authorization != null : hasPreviewMarkerCookie();
|
|
92
|
+
headers[PREVIEW_HEADER] = previewing ? 'true' : 'false';
|
|
93
|
+
|
|
94
|
+
// Public server fetches are all site content (per-session data is fetched
|
|
95
|
+
// client-side only), so opt them into Next's data cache — without this,
|
|
96
|
+
// Next 15 treats them as no-store and silently makes every page fully
|
|
97
|
+
// dynamic. The tags let the revalidate handler purge them. A preview render,
|
|
98
|
+
// though, carries an admin Authorization token: those fetches must stay live
|
|
99
|
+
// and never be written to or served from the shared data cache (a public
|
|
100
|
+
// request could otherwise be handed the hidden-fonts response), so opt them
|
|
101
|
+
// out with no-store. Both hints are inert outside Next.
|
|
102
|
+
//
|
|
103
|
+
// In local dev (`next dev`) we also skip the cache: the data cache plus
|
|
104
|
+
// on-demand `revalidateTag` don't reliably refresh, and the storefront proxy
|
|
105
|
+
// strips the browser's `no-cache`, so a stale entry can't be busted — leave
|
|
106
|
+
// dev fetches uncached so every render is fresh. See CACHE_IN_PRODUCTION.
|
|
107
|
+
const init = {
|
|
108
|
+
method: 'POST',
|
|
109
|
+
credentials: 'include',
|
|
110
|
+
headers,
|
|
111
|
+
body: JSON.stringify({
|
|
112
|
+
query: request.text,
|
|
113
|
+
variables
|
|
114
|
+
})
|
|
115
|
+
};
|
|
116
|
+
if (IS_SERVER && (previewing || !cacheInProduction())) {
|
|
117
|
+
init.cache = 'no-store';
|
|
118
|
+
} else if (IS_SERVER) {
|
|
119
|
+
init.cache = 'force-cache';
|
|
120
|
+
init.next = {
|
|
121
|
+
tags: ['graphql', ...((serverConfig === null || serverConfig === void 0 ? void 0 : serverConfig.cacheTags) ?? []), `operation:${request.name}`]
|
|
122
|
+
};
|
|
123
|
+
}
|
|
50
124
|
for (let attempt = 0; attempt <= 2; attempt++) {
|
|
51
125
|
try {
|
|
52
|
-
const resp = await fetch(url + `?queryName=${request.name}`,
|
|
53
|
-
method: 'POST',
|
|
54
|
-
credentials: 'include',
|
|
55
|
-
headers: {
|
|
56
|
-
...(serverConfig === null || serverConfig === void 0 ? void 0 : serverConfig.headers),
|
|
57
|
-
Accept: 'application/json',
|
|
58
|
-
'Content-Type': 'application/json',
|
|
59
|
-
'fontdue-stripe-integration': (options === null || options === void 0 ? void 0 : options.stripeIntegration) ?? STRIPE_INTEGRATION ?? 'dynamic',
|
|
60
|
-
'fontdue-client-version': version
|
|
61
|
-
},
|
|
62
|
-
body: JSON.stringify({
|
|
63
|
-
query: request.text,
|
|
64
|
-
variables
|
|
65
|
-
}),
|
|
66
|
-
// Server-side fetches are all site content (per-session data is
|
|
67
|
-
// fetched client-side only), so opt them into Next's data cache —
|
|
68
|
-
// without this, Next 15 treats them as no-store and silently makes
|
|
69
|
-
// every page fully dynamic. The tags below let the revalidate
|
|
70
|
-
// handler purge them when content changes. Inert outside Next:
|
|
71
|
-
// Node's fetch accepts and ignores this cache mode, and the
|
|
72
|
-
// browser path doesn't set it.
|
|
73
|
-
...(IS_SERVER ? {
|
|
74
|
-
cache: 'force-cache'
|
|
75
|
-
} : {}),
|
|
76
|
-
// @ts-ignore
|
|
77
|
-
next: {
|
|
78
|
-
tags: ['graphql', ...((serverConfig === null || serverConfig === void 0 ? void 0 : serverConfig.cacheTags) ?? []), `operation:${request.name}`]
|
|
79
|
-
}
|
|
80
|
-
});
|
|
126
|
+
const resp = await fetch(url + `?queryName=${request.name}`, init);
|
|
81
127
|
const json = await resp.json();
|
|
82
128
|
|
|
83
129
|
// GraphQL returns exceptions (for example, a missing required variable) in the "errors"
|
|
@@ -4,6 +4,18 @@ export interface SerializablePreloadedQuery<TQuery extends OperationType> {
|
|
|
4
4
|
variables: VariablesOf<TQuery>;
|
|
5
5
|
response: GraphQLResponse;
|
|
6
6
|
}
|
|
7
|
+
/** Per-call options shared by every preload helper. */
|
|
8
|
+
export interface LoadQueryOptions {
|
|
9
|
+
/** Override the GraphQL base URL for this fetch. */
|
|
10
|
+
url?: string;
|
|
11
|
+
/**
|
|
12
|
+
* Extra headers for this server-side fetch. Pass
|
|
13
|
+
* `previewAuthHeaders(token)` (from fontdue-js/preview) to reveal hidden
|
|
14
|
+
* fonts in preview when the render-scoped serverConfig store can't carry the
|
|
15
|
+
* token (Astro frontmatter, RR7 loaders — anything that isn't an RSC render).
|
|
16
|
+
*/
|
|
17
|
+
headers?: Record<string, string>;
|
|
18
|
+
}
|
|
7
19
|
/**
|
|
8
20
|
* RELAY CACHE KEY NORMALIZATION
|
|
9
21
|
*
|
|
@@ -43,7 +55,5 @@ export interface SerializablePreloadedQuery<TQuery extends OperationType> {
|
|
|
43
55
|
type RequireAllWithNull<T> = {
|
|
44
56
|
[K in keyof T]-?: T[K] | null;
|
|
45
57
|
};
|
|
46
|
-
export default function loadSerializableQuery<TQuery extends OperationType>(query: GraphQLTaggedNode, variables: RequireAllWithNull<VariablesOf<TQuery>>, options?:
|
|
47
|
-
url?: string;
|
|
48
|
-
}): Promise<SerializablePreloadedQuery<TQuery>>;
|
|
58
|
+
export default function loadSerializableQuery<TQuery extends OperationType>(query: GraphQLTaggedNode, variables: RequireAllWithNull<VariablesOf<TQuery>>, options?: LoadQueryOptions): Promise<SerializablePreloadedQuery<TQuery>>;
|
|
49
59
|
export {};
|
|
@@ -5,12 +5,10 @@ export interface FontdueServerConfig {
|
|
|
5
5
|
headers?: Record<string, string>;
|
|
6
6
|
/** Extra Next.js fetch cache tags applied to every server-side GraphQL fetch. */
|
|
7
7
|
cacheTags?: string[];
|
|
8
|
-
/**
|
|
9
|
-
* The site domain this render is for, when known. Set by
|
|
10
|
-
* fontdue-js/next's prepareFontdueRender so render-scoped helpers
|
|
11
|
-
* (currentFontdueEndpoint) can recover it.
|
|
12
|
-
*/
|
|
13
|
-
domain?: string;
|
|
14
8
|
}
|
|
9
|
+
type MaybePromise<T> = T | Promise<T>;
|
|
10
|
+
type AmbientConfigResolver = () => MaybePromise<FontdueServerConfig | undefined>;
|
|
15
11
|
export declare function setFontdueServerConfig(config: FontdueServerConfig): void;
|
|
16
|
-
export declare function
|
|
12
|
+
export declare function registerAmbientConfigResolver(resolver: AmbientConfigResolver): void;
|
|
13
|
+
export declare function resolveFontdueServerConfig(): Promise<FontdueServerConfig | undefined>;
|
|
14
|
+
export {};
|