This repository was archived by the owner on Jan 14, 2026. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 41
Expand file tree
/
Copy pathserver.js
More file actions
156 lines (137 loc) · 5.01 KB
/
server.js
File metadata and controls
156 lines (137 loc) · 5.01 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
import { createRequestHandler } from "@remix-run/cloudflare";
import * as remixBuild from "./build/server";
const handleRemixRequest = createRequestHandler(remixBuild);
const redirects = {
'/docs': 'https://developers.cloudflare.com/workers'
}
export default {
async fetch(request, env, ctx) {
const url = new URL(request.url);
if (redirects[url.pathname]) {
return Response.redirect(redirects[url.pathname])
}
// Try to serve static assets first using Workers Assets
if (env.ASSETS) {
try {
const asset = await env.ASSETS.fetch(request);
if (asset.status !== 404) {
// Set appropriate cache headers
const headers = new Headers(asset.headers);
const ttl = url.pathname.startsWith("/assets/")
? 60 * 60 * 24 * 365 // 1 year for assets
: 60 * 5; // 5 minutes for other files
headers.set('Cache-Control', `public, max-age=${ttl}`);
return new Response(asset.body, {
status: asset.status,
headers
});
}
} catch (error) {
// Asset not found, continue to Remix handler
}
}
if (url.pathname.startsWith("/api/v1/image-proxy")) {
const originalUrl = url.searchParams.get("url")
if (!originalUrl) {
return new Response("Missing url parameter", { status: 400 })
}
// Security: Validate URL to prevent SSRF attacks
const ALLOWED_DOMAINS = ['cdn.sanity.io', 'sanity.io'];
let parsedUrl;
try {
parsedUrl = new URL(originalUrl);
// Only allow HTTPS URLs from Sanity domains
if (parsedUrl.protocol !== 'https:') {
return new Response("Only HTTPS URLs are allowed", { status: 403 });
}
const isAllowed = ALLOWED_DOMAINS.some(domain =>
parsedUrl.hostname === domain || parsedUrl.hostname.endsWith(`.${domain}`)
);
if (!isAllowed) {
return new Response("URL not allowed", { status: 403 });
}
} catch (error) {
return new Response("Invalid URL", { status: 400 });
}
// Use cache with proper key
const cache = caches.default;
const cacheKey = new Request(originalUrl, { method: 'GET' });
const cachedResponse = await cache.match(cacheKey);
if (cachedResponse) {
// Add cache hit header
const headers = new Headers(cachedResponse.headers);
headers.set('X-Cache', 'HIT');
return new Response(cachedResponse.body, {
status: cachedResponse.status,
headers
});
}
// Fetch with timeout and error handling
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 10000); // 10 second timeout
const response = await fetch(originalUrl, {
signal: controller.signal,
headers: {
'User-Agent': 'CloudflareWorkers-ImageProxy/1.0'
}
});
clearTimeout(timeoutId);
// Validate response
if (!response.ok) {
return new Response(`Upstream error: ${response.status}`, {
status: response.status
});
}
// Validate content type is an image
const contentType = response.headers.get('content-type');
if (!contentType || !contentType.startsWith('image/')) {
return new Response("Invalid content type - only images are allowed", {
status: 400
});
}
// Add cache headers
const headers = new Headers(response.headers);
headers.set('Cache-Control', 'public, max-age=86400'); // 24 hours
headers.set('X-Cache', 'MISS');
const newResponse = new Response(response.body, {
status: response.status,
headers
});
// Store in cache
ctx.waitUntil(cache.put(cacheKey, newResponse.clone()));
return newResponse;
} catch (error) {
console.error('Image proxy error:', error);
if (error.name === 'AbortError') {
return new Response("Request timeout", { status: 504 });
}
return new Response("Proxy error", { status: 500 });
}
}
// Handle with Remix
try {
const loadContext = {
cloudflare: {
// This object matches the return value from Wrangler's
// `getPlatformProxy` used during development via Remix's
// `cloudflareDevProxyVitePlugin`:
// https://developers.cloudflare.com/workers/wrangler/api/#getplatformproxy
cf: request.cf,
ctx: {
waitUntil: ctx.waitUntil,
passThroughOnException: ctx.passThroughOnException,
},
caches,
env,
},
// Pass env directly for access to SANITY_TOKEN
env,
};
return await handleRemixRequest(request, loadContext);
} catch (error) {
console.log(error);
return new Response(error.toString(), { status: 500 });
}
},
};