-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathsite-worker.js
More file actions
103 lines (91 loc) · 4.07 KB
/
Copy pathsite-worker.js
File metadata and controls
103 lines (91 loc) · 4.07 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
// Thin wrapper around the static asset handler: adds a Link header on the
// homepage (RFC 8288 agent discovery) and serves docs/index.md when a client
// negotiates Accept: text/markdown for the homepage.
const HOMEPAGE_LINK =
'</.well-known/api-catalog>; rel="api-catalog", </skills/capture-screenshot.md>; rel="service-doc"';
// Same-origin proxy for the Ko-fi support widget. Some ad blockers filter
// requests to ko-fi.com / storage.ko-fi.com, which would silently remove the
// widget; serving it from our own origin avoids that. Only the two CDN image
// assets the widget actually uses are proxied (no open proxy).
const KOFI_CDN = 'https://storage.ko-fi.com/cdn';
const KOFI_ASSETS = new Set(['cup-border.png', 'whitelogo.svg']);
const KOFI_CACHE = { cf: { cacheEverything: true, cacheTtl: 86400 } };
async function proxyKofiWidget() {
const upstream = await fetch(`${KOFI_CDN}/widget/Widget_2.js`, KOFI_CACHE);
if (!upstream.ok) return new Response('Ko-fi widget unavailable', { status: 502 });
const script = (await upstream.text()).replaceAll(`${KOFI_CDN}/`, '/kofi-cdn/');
return new Response(script, {
headers: {
'content-type': 'application/javascript; charset=utf-8',
'cache-control': 'public, max-age=86400',
},
});
}
async function proxyKofiAsset(pathname) {
const name = pathname.slice('/kofi-cdn/'.length);
if (!KOFI_ASSETS.has(name)) return new Response('Not found', { status: 404 });
const upstream = await fetch(`${KOFI_CDN}/${name}`, KOFI_CACHE);
if (!upstream.ok) return new Response('Ko-fi asset unavailable', { status: 502 });
return new Response(upstream.body, {
headers: {
'content-type': upstream.headers.get('content-type') ?? 'application/octet-stream',
'cache-control': 'public, max-age=604800',
},
});
}
// Live user and star counts for the homepage proof line. Read from shields.io —
// the same source as the README badges — and cached at the edge, so the page
// itself never talks to a third party.
const STAT_SOURCES = {
users: 'https://img.shields.io/chrome-web-store/users/hdabbojjccojlapnfjpdppcpfcnhgmdp.json',
stars: 'https://img.shields.io/github/stars/pghqdev/OpenScreenShot.json',
version: 'https://img.shields.io/chrome-web-store/v/hdabbojjccojlapnfjpdppcpfcnhgmdp.json',
};
const STATS_TTL = 21600;
async function shieldValue(url, shape) {
try {
const upstream = await fetch(url, { cf: { cacheEverything: true, cacheTtl: STATS_TTL } });
if (!upstream.ok) return null;
const badge = await upstream.json();
return shape.test(badge.value ?? '') ? badge.value : null;
} catch {
return null;
}
}
async function siteStats() {
const [users, stars, version] = await Promise.all([
shieldValue(STAT_SOURCES.users, /^\d[\d,.kKmM+]*$/),
shieldValue(STAT_SOURCES.stars, /^\d[\d,.kKmM+]*$/),
shieldValue(STAT_SOURCES.version, /^v?\d+(\.\d+)*$/),
]);
return new Response(JSON.stringify({ users, stars, version }), {
headers: {
'content-type': 'application/json; charset=utf-8',
'cache-control': `public, max-age=${STATS_TTL}`,
},
});
}
async function route(url, request, env) {
const accept = request.headers.get('Accept') ?? '';
if (url.pathname === '/api/stats.json') return siteStats();
if (url.pathname === '/kofi-widget.js') return proxyKofiWidget();
if (url.pathname.startsWith('/kofi-cdn/')) return proxyKofiAsset(url.pathname);
if (url.pathname === '/' && accept.includes('text/markdown')) {
const md = await env.ASSETS.fetch(new URL('/index.md', url));
return new Response(md.body, {
status: md.status,
headers: { 'content-type': 'text/markdown; charset=utf-8' },
});
}
return env.ASSETS.fetch(request);
}
export default {
async fetch(request, env) {
const url = new URL(request.url);
const response = await route(url, request, env);
const headers = new Headers(response.headers);
headers.set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
if (url.pathname === '/') headers.set('Link', HOMEPAGE_LINK);
return new Response(response.body, { status: response.status, headers });
},
};