-
Notifications
You must be signed in to change notification settings - Fork 53
feat: support client hints #114
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -135,7 +135,30 @@ export default function ({ $device }) { | |||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| `clientHints.enabled` enables client hints feature.(default by false) | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| Note that the default user agent value is set to `Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.39 Safari/537.36`. | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| ## User-Agent Client Hints Support | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| To enable Client Hints, set clientHints.enabled options to true. | ||||||||||||||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| ### Client Side | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| `navigator.userAgentData` are referred to detect a device and a platform. | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| results from `navigator.userAgent` are overridden. | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| ### Server Side | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| the following request headers are referred to detect a device and a platform. | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| - sec-ch-ua | ||||||||||||||||||||||||||||||||||||||||||||||
| - sec-ch-mobile | ||||||||||||||||||||||||||||||||||||||||||||||
| - sec-ch-platform | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| results from user-agent header are overridden. | ||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+150
to
+160
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| ## CloudFront Support | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| If a user-agent is `Amazon CloudFront`, this module checks | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -60,24 +60,25 @@ function getBrowserName(a) { | |||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| const DEFAULT_USER_AGENT = '<%= options.defaultUserAgent %>' | ||||||||||||||||||||||||||||
| const REFRESH_ON_RESIZE = <%= options.refreshOnResize %> | ||||||||||||||||||||||||||||
| const USE_CLIENT_HINT = <%= options.clientHints.enabled %> | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| function extractDevices (ctx, userAgent = DEFAULT_USER_AGENT) { | ||||||||||||||||||||||||||||
| function extractDevices (headers, userAgent = DEFAULT_USER_AGENT) { | ||||||||||||||||||||||||||||
| let mobile = null | ||||||||||||||||||||||||||||
| let mobileOrTablet = null | ||||||||||||||||||||||||||||
| let ios = null | ||||||||||||||||||||||||||||
| let android = null | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| if (userAgent === 'Amazon CloudFront') { | ||||||||||||||||||||||||||||
| if (ctx.req.headers['cloudfront-is-mobile-viewer'] === 'true') { | ||||||||||||||||||||||||||||
| if (headers['cloudfront-is-mobile-viewer'] === 'true') { | ||||||||||||||||||||||||||||
| mobile = true | ||||||||||||||||||||||||||||
| mobileOrTablet = true | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| if (ctx.req.headers['cloudfront-is-tablet-viewer'] === 'true') { | ||||||||||||||||||||||||||||
| if (headers['cloudfront-is-tablet-viewer'] === 'true') { | ||||||||||||||||||||||||||||
| mobile = false | ||||||||||||||||||||||||||||
| mobileOrTablet = true | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| } else if (ctx.req && ctx.req.headers['cf-device-type']) { // Cloudflare | ||||||||||||||||||||||||||||
| switch (ctx.req.headers['cf-device-type']) { | ||||||||||||||||||||||||||||
| } else if (headers['cf-device-type']) { // Cloudflare | ||||||||||||||||||||||||||||
| switch (headers['cf-device-type']) { | ||||||||||||||||||||||||||||
| case 'mobile': | ||||||||||||||||||||||||||||
| mobile = true | ||||||||||||||||||||||||||||
| mobileOrTablet = true | ||||||||||||||||||||||||||||
|
|
@@ -110,6 +111,73 @@ function extractDevices (ctx, userAgent = DEFAULT_USER_AGENT) { | |||||||||||||||||||||||||||
| return { mobile, mobileOrTablet, ios, android, windows, macOS, isSafari, isFirefox, isEdge, isChrome, isSamsung, isCrawler } | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| function extractFromUserAgentData(userAgentData) { | ||||||||||||||||||||||||||||
| const hasBrand = (brandName) => userAgentData.brands.some(b => b.brand === brandName) | ||||||||||||||||||||||||||||
| const platform = userAgentData.platform | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| const mobile = userAgentData.mobile | ||||||||||||||||||||||||||||
| let mobileOrTablet = undefined | ||||||||||||||||||||||||||||
| if (mobile) { | ||||||||||||||||||||||||||||
| mobileOrTablet = mobile | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| const ios = undefined | ||||||||||||||||||||||||||||
| const android = undefined | ||||||||||||||||||||||||||||
| const windows = platform === 'Windows' | ||||||||||||||||||||||||||||
| const macOS = platform === 'macOS' | ||||||||||||||||||||||||||||
| const isSafari = undefined | ||||||||||||||||||||||||||||
| const isFirefox = undefined | ||||||||||||||||||||||||||||
| const isEdge = hasBrand('Microsoft Edge') | ||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. these brands' list would benefit from being stored in a kind of enum |
||||||||||||||||||||||||||||
| const isChrome = hasBrand('Google Chrome') | ||||||||||||||||||||||||||||
| const isSamsung = undefined | ||||||||||||||||||||||||||||
| const isCrawler = undefined | ||||||||||||||||||||||||||||
| return deleteUndefinedProperties({ mobile, mobileOrTablet, ios, android, windows, macOS, isSafari, isFirefox, isEdge, isChrome, isSamsung, isCrawler }) | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| const REGEX_CLIENT_HINT_BRAND = /"([^"]*)";v="([^"]*)"/ | ||||||||||||||||||||||||||||
| function extractFromUserHint(headers) { | ||||||||||||||||||||||||||||
| const uaHeader = headers['sec-ch-ua'] | ||||||||||||||||||||||||||||
| const mobileHeader = headers['sec-ch-ua-mobile'] | ||||||||||||||||||||||||||||
| const platform = headers['sec-ch-ua-platform'] | ||||||||||||||||||||||||||||
| if (typeof uaHeader === 'undefined') { | ||||||||||||||||||||||||||||
| return {} | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| const brands = uaHeader.split(',').map(b => b.trim()).map(brandStr => { | ||||||||||||||||||||||||||||
| const parsed = brandStr.match(REGEX_CLIENT_HINT_BRAND) | ||||||||||||||||||||||||||||
| console.log(brandStr, parsed) | ||||||||||||||||||||||||||||
| return {brand: parsed[1], version: parsed[2]} | ||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||
|
Comment on lines
+144
to
+148
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
this part isn't defensive enough imo |
||||||||||||||||||||||||||||
| const hasBrand = (brandName) => brands.some(b => b.brand === brandName) | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| let mobile = undefined | ||||||||||||||||||||||||||||
| if (mobileHeader) { | ||||||||||||||||||||||||||||
| mobile = mobileHeader === '?1' | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| let mobileOrTablet = undefined | ||||||||||||||||||||||||||||
| if (mobile) { | ||||||||||||||||||||||||||||
| mobileOrTablet = mobile | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| const ios = undefined | ||||||||||||||||||||||||||||
| const android = undefined | ||||||||||||||||||||||||||||
| const windows = platform === 'Windows' | ||||||||||||||||||||||||||||
| const macOS = platform === 'macOS' | ||||||||||||||||||||||||||||
| const isSafari = undefined | ||||||||||||||||||||||||||||
| const isFirefox = undefined | ||||||||||||||||||||||||||||
| const isEdge = hasBrand('Microsoft Edge') | ||||||||||||||||||||||||||||
| const isChrome = hasBrand('Google Chrome') | ||||||||||||||||||||||||||||
| const isSamsung = undefined | ||||||||||||||||||||||||||||
| const isCrawler = undefined | ||||||||||||||||||||||||||||
| return deleteUndefinedProperties({ mobile, mobileOrTablet, ios, android, windows, macOS, isSafari, isFirefox, isEdge, isChrome, isSamsung, isCrawler }) | ||||||||||||||||||||||||||||
|
Comment on lines
+159
to
+169
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why setting these to |
||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| function deleteUndefinedProperties(obj) { | ||||||||||||||||||||||||||||
| for (const key of Object.keys(obj)) { | ||||||||||||||||||||||||||||
| if (typeof obj[key] === 'undefined') { | ||||||||||||||||||||||||||||
| delete obj[key] | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| return obj | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
Comment on lines
+172
to
+179
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
💅 |
||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| export default async function (ctx, inject) { | ||||||||||||||||||||||||||||
| const makeFlags = () => { | ||||||||||||||||||||||||||||
| let userAgent = '' | ||||||||||||||||||||||||||||
|
|
@@ -118,10 +186,24 @@ export default async function (ctx, inject) { | |||||||||||||||||||||||||||
| } else if (typeof navigator !== 'undefined') { | ||||||||||||||||||||||||||||
| userAgent = navigator.userAgent | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| const { mobile, mobileOrTablet, ios, android, windows, macOS, isSafari, isFirefox, isEdge, isChrome, isSamsung, isCrawler } = extractDevices(ctx, userAgent) | ||||||||||||||||||||||||||||
| let headers = {} | ||||||||||||||||||||||||||||
| if (ctx && ctx.req) { | ||||||||||||||||||||||||||||
| headers = ctx.req.headers | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| const uaResult = extractDevices(headers, userAgent) | ||||||||||||||||||||||||||||
| let result = uaResult | ||||||||||||||||||||||||||||
| if (USE_CLIENT_HINT) { | ||||||||||||||||||||||||||||
| if (typeof navigator !== 'undefined' && typeof navigator.userAgentData !== 'undefined') { | ||||||||||||||||||||||||||||
| Object.assign(result, extractFromUserAgentData(navigator.userAgentData)) | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| Object.assign(result, extractFromUserHint(headers)) | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| const { mobile, mobileOrTablet, ios, android, windows, macOS, isSafari, isFirefox, isEdge, isChrome, isSamsung, isCrawler } = result | ||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||
| <% if (options.test) { %> | ||||||||||||||||||||||||||||
| extractDevices, | ||||||||||||||||||||||||||||
| extractFromUserAgentData, | ||||||||||||||||||||||||||||
| extractFromUserHint, | ||||||||||||||||||||||||||||
| <% } %> | ||||||||||||||||||||||||||||
| userAgent, | ||||||||||||||||||||||||||||
| isMobile: mobile, | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
😄