Skip to content

Commit 89f8e16

Browse files
authored
Cloudflare: resize images in v2 using direct fetch (#3141)
1 parent a3e1125 commit 89f8e16

File tree

21 files changed

+262
-163
lines changed

21 files changed

+262
-163
lines changed

Diff for: .github/composite/deploy-cloudflare/action.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ runs:
5555
GITBOOK_INTEGRATIONS_HOST: ${{ inputs.opItem }}/GITBOOK_INTEGRATIONS_HOST
5656
GITBOOK_IMAGE_RESIZE_SIGNING_KEY: ${{ inputs.opItem }}/GITBOOK_IMAGE_RESIZE_SIGNING_KEY
5757
GITBOOK_IMAGE_RESIZE_URL: ${{ inputs.opItem }}/GITBOOK_IMAGE_RESIZE_URL
58+
GITBOOK_IMAGE_RESIZE_MODE: ${{ inputs.opItem }}/GITBOOK_IMAGE_RESIZE_MODE
5859
GITBOOK_ASSETS_PREFIX: ${{ inputs.opItem }}/GITBOOK_ASSETS_PREFIX
5960
GITBOOK_FONTS_URL: ${{ inputs.opItem }}/GITBOOK_FONTS_URL
6061
- name: Build worker

Diff for: .github/composite/deploy-vercel/action.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ runs:
5454
GITBOOK_INTEGRATIONS_HOST: ${{ inputs.opItem }}/GITBOOK_INTEGRATIONS_HOST
5555
GITBOOK_IMAGE_RESIZE_SIGNING_KEY: ${{ inputs.opItem }}/GITBOOK_IMAGE_RESIZE_SIGNING_KEY
5656
GITBOOK_IMAGE_RESIZE_URL: ${{ inputs.opItem }}/GITBOOK_IMAGE_RESIZE_URL
57+
GITBOOK_IMAGE_RESIZE_MODE: ${{ inputs.opItem }}/GITBOOK_IMAGE_RESIZE_MODE
5758
GITBOOK_ASSETS_PREFIX: ${{ inputs.opItem }}/GITBOOK_ASSETS_PREFIX
5859
GITBOOK_FONTS_URL: ${{ inputs.opItem }}/GITBOOK_FONTS_URL
5960
- name: Build Project Artifacts

Diff for: README.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -59,15 +59,15 @@ bun install
5959
4. Start your local development server.
6060

6161
```
62-
bun dev
62+
bun dev:v2
6363
```
6464

6565
5. Open a published GitBook space in your web browser, prefixing it with `http://localhost:3000/`.
6666

6767
examples:
6868

69-
- http://localhost:3000/docs.gitbook.com
70-
- http://localhost:3000/open-source.gitbook.io/midjourney
69+
- http://localhost:3000/url/docs.gitbook.com
70+
- http://localhost:3000/url/open-source.gitbook.io/midjourney
7171

7272
Any published GitBook site can be accessed through your local development instance, and any updates you make to the codebase will be reflected in your browser.
7373

Diff for: bun.lock

+11-10
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@
145145
"@gitbook/api": "*",
146146
"@gitbook/cache-tags": "workspace:*",
147147
"@sindresorhus/fnv1a": "^3.1.0",
148+
"assert-never": "^1.2.1",
148149
"jwt-decode": "^4.0.0",
149150
"next": "canary",
150151
"react": "^19.0.0",
@@ -4017,7 +4018,7 @@
40174018

40184019
"gaxios/node-fetch": ["[email protected]", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="],
40194020

4020-
"gitbook-v2/next": ["[email protected]", "", { "dependencies": { "@next/env": "15.3.1-canary.1", "@swc/counter": "0.1.3", "@swc/helpers": "0.5.15", "busboy": "1.6.0", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "15.3.1-canary.1", "@next/swc-darwin-x64": "15.3.1-canary.1", "@next/swc-linux-arm64-gnu": "15.3.1-canary.1", "@next/swc-linux-arm64-musl": "15.3.1-canary.1", "@next/swc-linux-x64-gnu": "15.3.1-canary.1", "@next/swc-linux-x64-musl": "15.3.1-canary.1", "@next/swc-win32-arm64-msvc": "15.3.1-canary.1", "@next/swc-win32-x64-msvc": "15.3.1-canary.1", "sharp": "^0.34.1" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.41.2", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-J6xERAGPJVvHq61masP56+cQYzF3Ey72QiU0dz1aDPATMyX+Xs8lANqEbbX0Qj1mRLPl6D9YB+kAP1nZwV1K7Q=="],
4021+
"gitbook-v2/next": ["[email protected]", "", { "dependencies": { "@next/env": "15.3.1-canary.4", "@swc/counter": "0.1.3", "@swc/helpers": "0.5.15", "busboy": "1.6.0", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "15.3.1-canary.4", "@next/swc-darwin-x64": "15.3.1-canary.4", "@next/swc-linux-arm64-gnu": "15.3.1-canary.4", "@next/swc-linux-arm64-musl": "15.3.1-canary.4", "@next/swc-linux-x64-gnu": "15.3.1-canary.4", "@next/swc-linux-x64-musl": "15.3.1-canary.4", "@next/swc-win32-arm64-msvc": "15.3.1-canary.4", "@next/swc-win32-x64-msvc": "15.3.1-canary.4", "sharp": "^0.34.1" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.41.2", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-bVUdzmbfd3gEu30Riin463JNwTRbcOY/DlCY/+WhgwUR6Sfu1uJeyl3kGAjw58I+ZoNX2omJVpTd9AMluZ26qA=="],
40214022

40224023
"global-dirs/ini": ["[email protected]", "", {}, "sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ=="],
40234024

@@ -4885,23 +4886,23 @@
48854886

48864887
"gaxios/https-proxy-agent/debug": ["[email protected]", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ=="],
48874888

4888-
"gitbook-v2/next/@next/env": ["@next/[email protected].1", "", {}, "sha512-sPJsmQ9JIVBY7rOVw7Q2STByyQoS9707ZoZG16ApV72t22Vj89m015MWaDcFWbew4qCN6PCQYnFdQB3Nx4d3ww=="],
4889+
"gitbook-v2/next/@next/env": ["@next/[email protected].4", "", {}, "sha512-Bw464vR3fVUrhHQOh0o0ilXQ6wg6OvsNn3afUTjm1a0n0JmJJ+n9M1041xmBHSBGWUfTe74HepSmy79sF8EDlA=="],
48894890

4890-
"gitbook-v2/next/@next/swc-darwin-arm64": ["@next/[email protected].1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ALMi3dK7zguKMGweSzJOkderPxGV7wERDazXxCSxNS93K0D45GYRvNWxFi36PonOt8C/SF6l9JN3oryWFWxErA=="],
4891+
"gitbook-v2/next/@next/swc-darwin-arm64": ["@next/[email protected].4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ZR497D00W62Tf566uakD+VvA/2Cmagix3suVlB7TxLSPxXFAU5DeGQvaT9jkP+x1lEKggcZ+chI1CB9CcjvlTQ=="],
48914892

4892-
"gitbook-v2/next/@next/swc-darwin-x64": ["@next/[email protected].1", "", { "os": "darwin", "cpu": "x64" }, "sha512-Dxuvwbyl5Akgu4AC73YUgwCnfxmucEdlqnzLMLjMp4f2dlfs2H3xmCQbI/p/jfpK3MSr+NtyqfRy8hxKjfRBLw=="],
4893+
"gitbook-v2/next/@next/swc-darwin-x64": ["@next/[email protected].4", "", { "os": "darwin", "cpu": "x64" }, "sha512-dX0X7NG6goshCgbKsSzn6wVzgkRlUqRXMjO9E46B6HqcV/L8l15rMVhWr8zBzDt/7gEmAylnlnML8Zqv9BiF1Q=="],
48934894

4894-
"gitbook-v2/next/@next/swc-linux-arm64-gnu": ["@next/[email protected].1", "", { "os": "linux", "cpu": "arm64" }, "sha512-YTKsRz7oVIKNrpkbpTxDB9SuYJumELVncyG/f82SGGFuql5aE5jmQETNCATFYmAQvO2a3c02HF2lF1s7hEOg0A=="],
4895+
"gitbook-v2/next/@next/swc-linux-arm64-gnu": ["@next/[email protected].4", "", { "os": "linux", "cpu": "arm64" }, "sha512-ehE8KbvyBa7grVzC+n+L9CHbainjg362coYObhak3Jktnvb0uPf/m2kqgfN3UBzGSAczvcAuEbsLulFifAnFrw=="],
48954896

4896-
"gitbook-v2/next/@next/swc-linux-arm64-musl": ["@next/[email protected].1", "", { "os": "linux", "cpu": "arm64" }, "sha512-yIcd6YBSFz/lpJFSPpLxDpg7Tfyf/NxFeBBbYfmcjs2AZ0/+NUyAwFUfI1FKY5Lwym+bEfrEvhVWzmPZsYNQXA=="],
4897+
"gitbook-v2/next/@next/swc-linux-arm64-musl": ["@next/[email protected].4", "", { "os": "linux", "cpu": "arm64" }, "sha512-5NBstNVZeRP0OeRQGZqQGmrUdMVsMS556BekDeFqgwMqN/Ibq/EYwMMn1dkP26Dln9qIOj167Mde9CC2c3SQUg=="],
48974898

4898-
"gitbook-v2/next/@next/swc-linux-x64-gnu": ["@next/[email protected].1", "", { "os": "linux", "cpu": "x64" }, "sha512-2FKSD+vlBfyqpWJsOsBpsM+OB2iDybpPkONToGgFaurdbxue88lzrUuQg8FX/BxS5yjkP15mYubnTdBIxKa+yg=="],
4899+
"gitbook-v2/next/@next/swc-linux-x64-gnu": ["@next/[email protected].4", "", { "os": "linux", "cpu": "x64" }, "sha512-6CVq5yFWj2uZ3VgSl0OcCVVfH+EZhOqDocvCeXRaEvpVWq0s9zXPA6GgZghNY7Z5YSZXkB6z7Qi3xG+hzNLQ0A=="],
48994900

4900-
"gitbook-v2/next/@next/swc-linux-x64-musl": ["@next/[email protected].1", "", { "os": "linux", "cpu": "x64" }, "sha512-MpQLsXsEenGCCWZXMcCVmn7RqYTUNjS3gjPJL/8ILJhf2aKLzo1Z3XMepty9zzupd/E8bJQbegzOxxBYgriiKA=="],
4901+
"gitbook-v2/next/@next/swc-linux-x64-musl": ["@next/[email protected].4", "", { "os": "linux", "cpu": "x64" }, "sha512-3AiB/lUOa8QMsUM4KW0IYuMqvvMLQyANb13pGKihemtYUEjPS4/Jf5/vkz0qWF0AOP/1DNECdKpCIenU2C2pIA=="],
49014902

4902-
"gitbook-v2/next/@next/swc-win32-arm64-msvc": ["@next/[email protected].1", "", { "os": "win32", "cpu": "arm64" }, "sha512-NWYm76qsVKX5k/7MBiQy/fNK8tiEVvaAANC3x7VocLSphyozARoXpc15q3m2rexc5EY5/MwjQjrP81Awz5ldhQ=="],
4903+
"gitbook-v2/next/@next/swc-win32-arm64-msvc": ["@next/[email protected].4", "", { "os": "win32", "cpu": "arm64" }, "sha512-WACNwz1q2DtYqZXj+IjGME3D6XxRkkyuynMyC6d4tH5IahnxiwkJtfaRE1DwWWbVUPQ6TtevSEgCOh3eFk5yFQ=="],
49034904

4904-
"gitbook-v2/next/@next/swc-win32-x64-msvc": ["@next/[email protected].1", "", { "os": "win32", "cpu": "x64" }, "sha512-6piB1mQPtZpLuhgNoJuvpqIbqY9pOzoa3sG24hzK1H9ct9I4m3XQM2as3wyoe7PVJaYk6IZ/rfVU+WDfRhRxKQ=="],
4905+
"gitbook-v2/next/@next/swc-win32-x64-msvc": ["@next/[email protected].4", "", { "os": "win32", "cpu": "x64" }, "sha512-qbmYHxGD3MaI3OJ0NLRcBhfDoSnkvqqrZNmPIuPSlkOUlj+eIuyGtWMRocaYCGTEvQstlspymIsVzSfiyI+6ng=="],
49054906

49064907
"gitbook-v2/next/postcss": ["[email protected]", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="],
49074908

Diff for: packages/gitbook-v2/next.config.mjs

+1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ const nextConfig = {
3131
GITBOOK_ASSETS_PREFIX: process.env.GITBOOK_ASSETS_PREFIX,
3232
GITBOOK_SECRET: process.env.GITBOOK_SECRET,
3333
GITBOOK_IMAGE_RESIZE_SIGNING_KEY: process.env.GITBOOK_IMAGE_RESIZE_SIGNING_KEY,
34+
GITBOOK_IMAGE_RESIZE_MODE: process.env.GITBOOK_IMAGE_RESIZE_MODE,
3435
GITBOOK_FONTS_URL: process.env.GITBOOK_FONTS_URL,
3536
GITBOOK_RUNTIME: process.env.GITBOOK_RUNTIME,
3637

Diff for: packages/gitbook-v2/package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@
1212
"server-only": "^0.0.1",
1313
"warn-once": "^0.1.1",
1414
"rison": "^0.1.1",
15-
"jwt-decode": "^4.0.0"
15+
"jwt-decode": "^4.0.0",
16+
"assert-never": "^1.2.1"
1617
},
1718
"devDependencies": {
1819
"gitbook": "*",

Diff for: packages/gitbook-v2/src/lib/env/globals.ts

+18
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,15 @@ export const GITBOOK_IMAGE_RESIZE_URL = process.env.GITBOOK_IMAGE_RESIZE_URL ??
8585
export const GITBOOK_IMAGE_RESIZE_SIGNING_KEY =
8686
process.env.GITBOOK_IMAGE_RESIZE_SIGNING_KEY ?? null;
8787

88+
/**
89+
* Mode used for resizing images.
90+
*/
91+
export const GITBOOK_IMAGE_RESIZE_MODE = enforceEnum(
92+
'GITBOOK_IMAGE_RESIZE_MODE',
93+
process.env.GITBOOK_IMAGE_RESIZE_MODE || 'cdn-cgi',
94+
['cdn-cgi', 'cf-fetch']
95+
);
96+
8897
/**
8998
* Endpoint where icons are served.
9099
*/
@@ -100,3 +109,12 @@ export const GITBOOK_ICONS_TOKEN = process.env.GITBOOK_ICONS_TOKEN;
100109
* Secret used to validate requests from the GitBook app.
101110
*/
102111
export const GITBOOK_SECRET = process.env.GITBOOK_SECRET ?? null;
112+
113+
function enforceEnum<T extends string>(key: string, value: string, enumValues: T[]): T {
114+
if (!enumValues.includes(value as T)) {
115+
throw new Error(
116+
`Invalid value for ${key}: "${value}", expected one of: ${enumValues.join(', ')}`
117+
);
118+
}
119+
return value as T;
120+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { checkIsHttpURL } from '@/lib/urls';
2+
3+
/**
4+
* Check if an image URL is resizable.
5+
* Skip it for non-http(s) URLs (data, etc).
6+
* Skip it for SVGs.
7+
* Skip it for GitBook images (to avoid recursion).
8+
*/
9+
export function checkIsSizableImageURL(input: string): boolean {
10+
if (!URL.canParse(input)) {
11+
return false;
12+
}
13+
14+
if (input.includes('/~gitbook/image')) {
15+
return false;
16+
}
17+
18+
const parsed = new URL(input);
19+
if (parsed.pathname.endsWith('.svg') || parsed.pathname.endsWith('.avif')) {
20+
return false;
21+
}
22+
if (!checkIsHttpURL(parsed)) {
23+
return false;
24+
}
25+
26+
return true;
27+
}

Diff for: packages/gitbook-v2/src/lib/images/createImageResizer.ts

+2-143
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,11 @@
11
import 'server-only';
2-
32
import { GITBOOK_IMAGE_RESIZE_SIGNING_KEY, GITBOOK_IMAGE_RESIZE_URL } from '../env';
43
import type { GitBookLinker } from '../links';
4+
import { checkIsSizableImageURL } from './checkIsSizableImageURL';
5+
import { getImageSize } from './resizer';
56
import { type SignatureVersion, generateImageSignature } from './signatures';
67
import type { ImageResizer } from './types';
78

8-
interface CloudflareImageJsonFormat {
9-
width: number;
10-
height: number;
11-
original: {
12-
file_size: number;
13-
width: number;
14-
height: number;
15-
format: string;
16-
};
17-
}
18-
19-
/**
20-
* https://developers.cloudflare.com/images/image-resizing/resize-with-workers/
21-
*/
22-
export interface CloudflareImageOptions {
23-
format?: 'webp' | 'avif' | 'json' | 'jpeg';
24-
fit?: 'scale-down' | 'contain' | 'cover' | 'crop' | 'pad';
25-
width?: number;
26-
height?: number;
27-
dpr?: number;
28-
anim?: boolean;
29-
quality?: number;
30-
}
31-
329
/**
3310
* Create an image resizer for a rendering context.
3411
*/
@@ -106,124 +83,6 @@ export function createNoopImageResizer(): ImageResizer {
10683
};
10784
}
10885

109-
/**
110-
* Check if a URL is an HTTP URL.
111-
*/
112-
export function checkIsHttpURL(input: string | URL): boolean {
113-
if (!URL.canParse(input)) {
114-
return false;
115-
}
116-
const parsed = new URL(input);
117-
return parsed.protocol === 'http:' || parsed.protocol === 'https:';
118-
}
119-
120-
/**
121-
* Check if an image URL is resizable.
122-
* Skip it for non-http(s) URLs (data, etc).
123-
* Skip it for SVGs.
124-
* Skip it for GitBook images (to avoid recursion).
125-
*/
126-
export function checkIsSizableImageURL(input: string): boolean {
127-
if (!URL.canParse(input)) {
128-
return false;
129-
}
130-
131-
if (input.includes('/~gitbook/image')) {
132-
return false;
133-
}
134-
135-
const parsed = new URL(input);
136-
if (parsed.pathname.endsWith('.svg') || parsed.pathname.endsWith('.avif')) {
137-
return false;
138-
}
139-
if (!checkIsHttpURL(parsed)) {
140-
return false;
141-
}
142-
143-
return true;
144-
}
145-
146-
/**
147-
* Get the size of an image.
148-
*/
149-
export async function getImageSize(
150-
input: string,
151-
defaultSize: Partial<CloudflareImageOptions> = {}
152-
): Promise<{ width: number; height: number } | null> {
153-
if (!checkIsSizableImageURL(input)) {
154-
return null;
155-
}
156-
157-
try {
158-
const response = await resizeImage(input, {
159-
// Abort the request after 2 seconds to avoid blocking rendering for too long
160-
signal: AbortSignal.timeout(2000),
161-
// Measure size and resize it to the most common size
162-
// to optimize caching
163-
...defaultSize,
164-
format: 'json',
165-
anim: false,
166-
});
167-
168-
const json = (await response.json()) as CloudflareImageJsonFormat;
169-
return {
170-
width: json.original.width,
171-
height: json.original.height,
172-
};
173-
} catch (_error) {
174-
return null;
175-
}
176-
}
177-
178-
/**
179-
* Execute a Cloudflare Image Resize operation on an image.
180-
*/
181-
export async function resizeImage(
182-
input: string,
183-
options: CloudflareImageOptions & {
184-
signal?: AbortSignal;
185-
}
186-
): Promise<Response> {
187-
const { signal, ...resizeOptions } = options;
188-
189-
const parsed = new URL(input);
190-
if (parsed.protocol === 'data:') {
191-
throw new Error('Cannot resize data: URLs');
192-
}
193-
194-
if (parsed.hostname === 'localhost') {
195-
throw new Error('Cannot resize localhost URLs');
196-
}
197-
198-
// Since Cloudflare Images options on fetch are not supported on Cloudflare Pages,
199-
// we need to use the Cloudflare Image Resize API directly.
200-
if (!GITBOOK_IMAGE_RESIZE_URL) {
201-
throw new Error('GITBOOK_IMAGE_RESIZE_URL is not set');
202-
}
203-
204-
return await fetch(
205-
`${GITBOOK_IMAGE_RESIZE_URL}${stringifyOptions(
206-
resizeOptions
207-
)}/${encodeURIComponent(input)}`,
208-
{
209-
headers: {
210-
// Pass the `Accept` header, as Cloudflare uses this to validate the format.
211-
Accept:
212-
resizeOptions.format === 'json'
213-
? 'application/json'
214-
: `image/${resizeOptions.format || 'jpeg'}`,
215-
},
216-
signal,
217-
}
218-
);
219-
}
220-
221-
function stringifyOptions(options: CloudflareImageOptions): string {
222-
return Object.entries({ ...options }).reduce((rest, [key, value]) => {
223-
return `${rest}${rest ? ',' : ''}${key}=${value}`;
224-
}, '');
225-
}
226-
22786
/**
22887
* Because of a bug in Cloudflare, 127.0.0.1 is replaced by localhost.
22988
* We protect against it by converting to a special token, and then parsing

Diff for: packages/gitbook-v2/src/lib/images/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,5 @@ export * from './createImageResizer';
33
export * from './signatures';
44
export * from './utils';
55
export * from './getImageResizingContextId';
6+
export * from './resizer';
7+
export * from './checkIsSizableImageURL';
+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { GITBOOK_IMAGE_RESIZE_URL } from '@v2/lib/env';
2+
import type { CloudflareImageOptions } from './types';
3+
4+
/**
5+
* Resize an image by doing a request to a /cdn/cgi/ endpoint.
6+
* https://developers.cloudflare.com/images/transform-images/transform-via-url/
7+
*/
8+
export async function resizeImageWithCDNCgi(
9+
input: string,
10+
options: CloudflareImageOptions & {
11+
signal?: AbortSignal;
12+
}
13+
): Promise<Response> {
14+
const { signal, ...resizeOptions } = options;
15+
16+
// Since Cloudflare Images options on fetch are not supported on Cloudflare Pages,
17+
// we need to use the Cloudflare Image Resize API directly.
18+
if (!GITBOOK_IMAGE_RESIZE_URL) {
19+
throw new Error('GITBOOK_IMAGE_RESIZE_URL is not set for cdn-cgi image resize mode');
20+
}
21+
22+
const resizeURL = `${GITBOOK_IMAGE_RESIZE_URL}${stringifyOptions(
23+
resizeOptions
24+
)}/${encodeURIComponent(input)}`;
25+
26+
// biome-ignore lint/suspicious/noConsole: this log is useful for debugging
27+
console.log(`resize image using cdn-cgi: ${resizeURL}`);
28+
29+
return await fetch(resizeURL, {
30+
headers: {
31+
// Pass the `Accept` header, as Cloudflare uses this to validate the format.
32+
Accept:
33+
resizeOptions.format === 'json'
34+
? 'application/json'
35+
: `image/${resizeOptions.format || 'jpeg'}`,
36+
},
37+
signal,
38+
});
39+
}
40+
41+
function stringifyOptions(options: CloudflareImageOptions): string {
42+
return Object.entries({ ...options }).reduce((rest, [key, value]) => {
43+
return `${rest}${rest ? ',' : ''}${key}=${value}`;
44+
}, '');
45+
}

0 commit comments

Comments
 (0)