-
Notifications
You must be signed in to change notification settings - Fork 11
Expand file tree
/
Copy pathindex.ts
More file actions
102 lines (90 loc) · 3.16 KB
/
index.ts
File metadata and controls
102 lines (90 loc) · 3.16 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
import { ClientHint, ClientHintsValue } from './utils.js'
export type { ClientHint, ClientHintsValue }
export function getHintUtils<Hints extends Record<string, ClientHint<any>>>(
hints: Hints,
) {
function getCookieValue(cookieString: string, name: string) {
const hint = hints[name]
if (!hint) {
throw new Error(
`Unknown client hint: ${typeof name === 'string' ? name : 'Unknown'}`,
)
}
const value = cookieString
.split(';')
.map((c: string) => c.trim())
.find((c: string) => c.startsWith(hint.cookieName + '='))
?.split('=')[1]
return value ? decodeURIComponent(value) : null
}
function getHints(request?: Request): ClientHintsValue<Hints> {
const cookieString =
typeof document !== 'undefined'
? document.cookie
: typeof request !== 'undefined'
? request.headers.get('Cookie') ?? ''
: ''
return Object.entries(hints).reduce((acc, [name, hint]) => {
const hintName = name
if ('transform' in hint) {
// @ts-expect-error - this is fine (PRs welcome though)
acc[hintName] = hint.transform(
getCookieValue(cookieString, hintName) ?? hint.fallback,
)
} else {
// @ts-expect-error - this is fine (PRs welcome though)
acc[hintName] = getCookieValue(cookieString, hintName) ?? hint.fallback
}
return acc
}, {} as ClientHintsValue<Hints>)
}
/**
* This returns a string of JavaScript that can be used to check if the client
* hints have changed and will reload the page if they have.
*/
function getClientHintCheckScript() {
return `
// This block of code allows us to check if the client hints have changed and
// force a reload of the page with updated hints if they have so you don't get
// a flash of incorrect content.
function checkClientHints() {
if (!navigator.cookieEnabled) return;
// set a short-lived cookie to make sure we can set cookies
document.cookie = "canSetCookies=1; Max-Age=60; SameSite=Lax; path=/";
const canSetCookies = document.cookie.includes("canSetCookies=1");
document.cookie = "canSetCookies=; Max-Age=-1; path=/";
if (!canSetCookies) return;
const cookies = document.cookie.split(';').map(c => c.trim()).reduce((acc, cur) => {
const [key, value] = cur.split('=');
acc[key] = value;
return acc;
}, {});
let cookieChanged = false;
const hints = [
${Object.values(hints)
.map((hint) => {
const cookieName = JSON.stringify(hint.cookieName)
return `{ name: ${cookieName}, actual: String(${hint.getValueCode}), value: cookies[${cookieName}] != null ? cookies[${cookieName}] : encodeURIComponent("${hint.fallback}") }`
})
.join(',\n')}
];
for (const hint of hints) {
document.cookie = encodeURIComponent(hint.name) + '=' + encodeURIComponent(hint.actual) + '; Max-Age=31536000; SameSite=Lax; path=/';
if (decodeURIComponent(hint.value) !== hint.actual) {
cookieChanged = true;
}
}
if (cookieChanged) {
// Hide the page content immediately to prevent visual flicker
const style = document.createElement('style');
style.textContent = 'html { visibility: hidden !important; }';
document.head.appendChild(style);
// Trigger the reload
window.location.reload();
}
}
checkClientHints();
`
}
return { getHints, getClientHintCheckScript }
}