Skip to content

Commit 34e3fde

Browse files
committed
chore: add headers extraction helper
1 parent e711fbe commit 34e3fde

File tree

4 files changed

+104
-46
lines changed

4 files changed

+104
-46
lines changed

playground/nuxt.config.ts

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export default defineNuxtConfig({
99
device: 'memory',
1010
critical: {
1111
width: true,
12+
viewportSize: true,
1213
prefersColorScheme: true,
1314
},
1415
},

src/runtime/plugins/critical.server.ts

+44-33
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import type {
66
CriticalClientHintsConfiguration,
77
} from '../shared-types/types'
88
import { useHttpClientHintsState } from './state'
9-
import { writeClientHintHeaders, writeHeaders } from './headers'
9+
import { lookupHeader, writeClientHintHeaders, writeHeaders } from './headers'
1010
import {
1111
defineNuxtPlugin,
1212
useCookie,
@@ -171,8 +171,12 @@ function lookupClientHints(
171171
// Since sec-ch-ua-mobile is a low entropy header, we don't need to include it in Accept-CH,
172172
// the user agent will send it always unless blocked by a user agent permission policy, check:
173173
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Sec-CH-UA-Mobile
174-
const mobileHeader = headers[SecChUaMobile]
175-
if (mobileHeader === '?1')
174+
const mobileHeader = lookupHeader(
175+
'boolean',
176+
SecChUaMobile,
177+
headers,
178+
)
179+
if (mobileHeader)
176180
features.devicePixelRatioAvailable = browserFeatureAvailable(userAgent, 'devicePixelRatio')
177181
}
178182

@@ -239,48 +243,56 @@ function collectClientHints(
239243
}
240244

241245
if (hints.viewportHeightAvailable && criticalClientHintsConfiguration.viewportSize) {
242-
const header = headers[AcceptClientHintsRequestHeaders.viewportHeight]
243-
if (header) {
246+
const viewportHeight = lookupHeader(
247+
'int',
248+
AcceptClientHintsRequestHeaders.viewportHeight,
249+
headers,
250+
)
251+
if (typeof viewportHeight === 'number') {
244252
hints.firstRequest = false
245-
try {
246-
hints.viewportHeight = Number.parseInt(header)
247-
}
248-
catch {
249-
hints.viewportHeight = criticalClientHintsConfiguration.clientHeight
250-
}
253+
hints.viewportHeight = viewportHeight
254+
}
255+
else {
256+
hints.viewportHeight = criticalClientHintsConfiguration.clientHeight
251257
}
252258
}
253259
else {
254260
hints.viewportHeight = criticalClientHintsConfiguration.clientHeight
255261
}
256262

257263
if (hints.viewportWidthAvailable && criticalClientHintsConfiguration.viewportSize) {
258-
const header = headers[AcceptClientHintsRequestHeaders.viewportWidth]
259-
if (header) {
264+
const viewportWidth = lookupHeader(
265+
'int',
266+
AcceptClientHintsRequestHeaders.viewportWidth,
267+
headers,
268+
)
269+
if (typeof viewportWidth === 'number') {
260270
hints.firstRequest = false
261-
try {
262-
hints.viewportWidth = Number.parseInt(header)
263-
}
264-
catch {
265-
hints.viewportWidth = criticalClientHintsConfiguration.clientWidth
266-
}
271+
hints.viewportWidth = viewportWidth
272+
}
273+
else {
274+
hints.viewportWidth = criticalClientHintsConfiguration.clientWidth
267275
}
268276
}
269277
else {
270278
hints.viewportWidth = criticalClientHintsConfiguration.clientWidth
271279
}
272280

273281
if (hints.devicePixelRatioAvailable && criticalClientHintsConfiguration.viewportSize) {
274-
const header = headers[AcceptClientHintsRequestHeaders.devicePixelRatio]
275-
if (header) {
282+
const devicePixelRatio = lookupHeader(
283+
'float',
284+
AcceptClientHintsRequestHeaders.devicePixelRatio,
285+
headers,
286+
)
287+
if (typeof devicePixelRatio === 'number') {
276288
hints.firstRequest = false
277289
try {
278-
hints.devicePixelRatio = Number.parseFloat(header)
279-
if (!Number.isNaN(hints.devicePixelRatio) && hints.devicePixelRatio > 0) {
290+
hints.devicePixelRatio = devicePixelRatio
291+
if (!Number.isNaN(devicePixelRatio) && devicePixelRatio > 0) {
280292
if (typeof hints.viewportWidth === 'number')
281-
hints.viewportWidth = Math.round(hints.viewportWidth / hints.devicePixelRatio)
293+
hints.viewportWidth = Math.round(hints.viewportWidth / devicePixelRatio)
282294
if (typeof hints.viewportHeight === 'number')
283-
hints.viewportHeight = Math.round(hints.viewportHeight / hints.devicePixelRatio)
295+
hints.viewportHeight = Math.round(hints.viewportHeight / devicePixelRatio)
284296
}
285297
}
286298
catch {
@@ -290,15 +302,14 @@ function collectClientHints(
290302
}
291303

292304
if (hints.widthAvailable && criticalClientHintsConfiguration.width) {
293-
const header = headers[AcceptClientHintsRequestHeaders.width]
294-
if (header) {
305+
const width = lookupHeader(
306+
'int',
307+
AcceptClientHintsRequestHeaders.width,
308+
headers,
309+
)
310+
if (typeof width === 'number') {
295311
hints.firstRequest = false
296-
try {
297-
hints.width = Number.parseInt(header)
298-
}
299-
catch {
300-
// just ignore
301-
}
312+
hints.width = width
302313
}
303314
}
304315

src/runtime/plugins/device.server.ts

+14-13
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,17 @@ import type {
66
ResolvedHttpClientHintsOptions,
77
} from '../shared-types/types'
88
import { useHttpClientHintsState } from './state'
9-
import { writeClientHintHeaders, writeHeaders } from './headers'
9+
import { type GetHeaderType, lookupHeader, writeClientHintHeaders, writeHeaders } from './headers'
1010
import { defineNuxtPlugin, useRequestHeaders, useRuntimeConfig } from '#imports'
1111

1212
const DeviceClientHintsHeaders: Record<DeviceHints, string> = {
1313
memory: 'Device-Memory',
1414
}
1515

16+
const DeviceClientHintsHeadersTypes: Record<DeviceHints, GetHeaderType> = {
17+
memory: 'float',
18+
}
19+
1620
type DeviceClientHintsHeadersKey = keyof typeof DeviceClientHintsHeaders
1721

1822
const AcceptClientHintsRequestHeaders = Object.entries(DeviceClientHintsHeaders).reduce((acc, [key, value]) => {
@@ -119,18 +123,15 @@ function collectClientHints(
119123
const hints = lookupClientHints(userAgent, deviceHints)
120124

121125
for (const hint of deviceHints) {
122-
// TODO: review this logic, we need some helpers to parse headers
123-
if (hint === 'memory') {
124-
if (hints.memoryAvailable) {
125-
const header = headers[AcceptClientHintsRequestHeaders.memory]
126-
if (header) {
127-
try {
128-
hints.memory = Number.parseFloat(header)
129-
}
130-
catch {
131-
// just ignore
132-
}
133-
}
126+
if (hints[`${hint}Available`]) {
127+
const value = lookupHeader(
128+
DeviceClientHintsHeadersTypes[hint],
129+
AcceptClientHintsRequestHeaders[hint],
130+
headers,
131+
)
132+
console.log({ hint, value })
133+
if (typeof value !== 'undefined') {
134+
hints[hint] = value as typeof hints[typeof hint]
134135
}
135136
}
136137
}

src/runtime/plugins/headers.ts

+45
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,48 @@ export function writeHeaders(headers: Record<string, string[]>) {
2626
return callback()
2727
})
2828
}
29+
30+
export type GetHeaderType = 'string' | 'int' | 'float' | 'boolean'
31+
type GetHeaderReturnType<T extends GetHeaderType> = T extends 'string'
32+
? string
33+
: T extends 'int'
34+
? number
35+
: T extends 'float'
36+
? number
37+
: T extends 'boolean'
38+
? boolean
39+
: never
40+
41+
export function lookupHeader<T extends GetHeaderType>(
42+
type: T,
43+
key: Lowercase<string>,
44+
headers: { [key in Lowercase<string>]?: string | undefined },
45+
): GetHeaderReturnType<T> | undefined {
46+
const value = headers[key]
47+
if (!value)
48+
return undefined
49+
50+
if (type === 'string')
51+
return value as GetHeaderReturnType<T>
52+
53+
if (type === 'int' || type === 'float') {
54+
try {
55+
const numberValue = type === 'int'
56+
? Number.parseInt(value)
57+
: Number.parseFloat(value)
58+
return Number.isNaN(numberValue)
59+
? undefined
60+
: numberValue as GetHeaderReturnType<T>
61+
}
62+
catch {
63+
return undefined
64+
}
65+
}
66+
67+
if (type === 'boolean') {
68+
const booleanValue = value === '?1'
69+
return booleanValue as GetHeaderReturnType<T>
70+
}
71+
72+
return undefined
73+
}

0 commit comments

Comments
 (0)