Skip to content

Commit

Permalink
cleanup, replace caniuse with caniuselite
Browse files Browse the repository at this point in the history
  • Loading branch information
nonrational committed Oct 19, 2024
1 parent 41f19b2 commit c0fe248
Show file tree
Hide file tree
Showing 9 changed files with 217 additions and 156 deletions.
1 change: 1 addition & 0 deletions deno.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"@std/csv": "jsr:@std/csv@^1.0.3",
"@std/semver": "jsr:@std/semver@^1.0.3",
"caniuse-db": "npm:caniuse-db@^1.0.30001669",
"caniuse-lite": "npm:caniuse-lite@^1.0.30001669",
"preact": "https://esm.sh/preact@10.22.0",
"preact/": "https://esm.sh/preact@10.22.0/",
"tailwindcss": "npm:tailwindcss@3.4.1",
Expand Down
66 changes: 66 additions & 0 deletions lib/agent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import data, { type AgentLiteKey, type MajorVersionStr, type UsageFloat } from './caniuse_lite.ts'
import { UserAgent } from '$std/http/user_agent.ts'
import { epochToDate } from './utils.ts'

const agents = data.agents

const BROWSER_NAME_AGENT_KEYS: Record<string, AgentLiteKey> = {
'Chrome': 'chrome',
'Mobile Firefox': 'and_ff',
'Mobile Chrome': 'and_chr',
'DuckDuckGo': 'chrome',
'Edge': 'edge',
'Firefox': 'firefox',
'IE': 'ie',
'Opera': 'opera',
'Opera Mini': 'op_mini',
'QQ Browser': 'and_qq',
'Safari': 'safari',
'Mobile Safari': 'ios_saf',
'Samsung Internet': 'samsung',
'UC': 'and_uc',
}

export type AgentRelease = {
name: string
version: MajorVersionStr
userAgent?: UserAgent | null
releaseDate: Date
usage: UsageFloat
currentVersion: MajorVersionStr
}

const findAgentStats = (name: string) => agents[BROWSER_NAME_AGENT_KEYS[name]]

export const getAgentReleaseInfo = (ua: string): AgentRelease => {
const userAgent = new UserAgent(ua)

const name = userAgent.browser?.name
const version = userAgent.browser?.major as MajorVersionStr

if (!name || !version) {
return {
name: 'Unknown',
userAgent,
version: '0',
releaseDate: new Date(0),
usage: 0,
currentVersion: '0',
}
}

const agentStats = findAgentStats(name)

const [lastReleaseVersion] = Object.entries(agentStats.release_date)
.filter(([_, date]) => date !== null)
.toSorted(([versionA], [versionB]) => parseFloat(versionB) - parseFloat(versionA))[0]

return {
name,
version,
userAgent,
releaseDate: epochToDate(agentStats.release_date[version]),
usage: agentStats.usage_global[version],
currentVersion: lastReleaseVersion,
}
}
116 changes: 39 additions & 77 deletions lib/browsers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,8 @@ import * as semver from '@std/semver'
import type { Agent, AgentKey, AgentVersion, VersionStr } from './caniuse.ts'
import usage from './browsers/usage.ts'
import caniuse from './caniuse.ts'

const BROWSER_NAME_AGENT_KEYS: Record<string, AgentKey> = {
'Chrome': 'chrome',
'Mobile Firefox': 'and_ff',
'Mobile Chrome': 'and_chr',
'DuckDuckGo': 'chrome',
'Edge': 'edge',
'Firefox': 'firefox',
'IE': 'ie',
'Opera': 'opera',
'Opera Mini': 'op_mini',
'QQ Browser': 'and_qq',
'Safari': 'safari',
'Mobile Safari': 'ios_saf',
'Samsung Internet': 'samsung',
'UC': 'and_uc',
}
import { epochToDate } from './utils.ts'
import { fuzzyParseSemVer } from './semver.ts'

const BROWSER_NAME_USAGE_KEYS: Record<string, string> = {
'Chrome': 'Chrome',
Expand All @@ -47,77 +32,54 @@ export const getFamilyUsage = (name: string): number => {
return usageKey ? usage.stats[usageKey] as number : 0
}

const epochToDate = (epoch: number) => new Date(epoch * 1000)
const countLiterals = (str: string, char: string) => str.split(char).length - 1
// export type KnownBrowserName = keyof typeof BROWSER_NAME_AGENT_KEYS

// A naive way to turn partial version strings into comparable semvers
export const toSemVer = (version: VersionStr): SemVer | SemVerRange => {
if (semver.tryParse(version)) return semver.parse(version)
// export const findAgent = (name: string): Agent | null => {
// const agentKey = BROWSER_NAME_AGENT_KEYS[name]

// If it looks like a range, then let's try to parse both sides loosely
if (countLiterals(version, '-') === 1) {
const minmax = version.split('-').map(toSemVer)
if (minmax.every(semver.isSemVer)) {
return semver.parseRange(minmax.map(semver.format).join('-'))
}
}
// if (agentKey) return caniuse.agents[agentKey]

version = version.replace(/\.+$/, '')
while (!semver.tryParse(version) && countLiterals(version, '.') <= 2) {
version += '.0'
}
// console.error(`Unknown agent: ${agentKey}`)
// return null
// }

return semver.parse(version)
}
// export const isKnownBrowser = (name?: string): boolean => name ? name in BROWSER_NAME_AGENT_KEYS : false
// export const isKnownRelease = (name: string, version: VersionStr) => {
// const agent = findAgent(name)
// return agent ? version in agent.usage_global : false
// }

export type KnownBrowserName = keyof typeof BROWSER_NAME_AGENT_KEYS
// export type AgentReleaseInfo = AgentVersion & {
// semver: SemVer | SemVerRange | null
// released_on?: Date
// }

export const findAgent = (name: string): Agent | null => {
const agentKey = BROWSER_NAME_AGENT_KEYS[name]
// const toAgentReleaseInfo = (av: AgentVersion): AgentReleaseInfo => {
// return { ...av, semver: fuzzyParseSemVer(av.version) }
// }

if (agentKey) return caniuse.agents[agentKey]
// const getAgentSemVers = (name: KnownBrowserName): AgentReleaseInfo[] => {
// const agent = findAgent(name)

console.error(`Unknown agent: ${agentKey}`)
return null
}
// return agent?.version_list ? agent.version_list.map(toAgentReleaseInfo) : []
// }

export const isKnownBrowser = (name?: string): boolean => name ? name in BROWSER_NAME_AGENT_KEYS : false
export const isKnownRelease = (name: string, version: VersionStr) => {
const agent = findAgent(name)
return agent ? version in agent.usage_global : false
}
// const semVerMatches = (needle: SemVer, haystack: SemVer | SemVerRange | null): boolean => {
// if (!haystack) return false

export type AgentReleaseInfo = AgentVersion & {
semver: SemVer | SemVerRange | null
released_on?: Date
}
// if (semver.isRange(haystack)) return semver.satisfies(needle, haystack)
// return semver.equals(needle, haystack)
// }

const toAgentReleaseInfo = (av: AgentVersion): AgentReleaseInfo => {
return { ...av, semver: toSemVer(av.version) }
}
// export const getAgentReleaseInfo = (name: KnownBrowserName, version: VersionStr): AgentReleaseInfo | null => {
// const needle = fuzzyParseSemVer(version) as SemVer

const getAgentSemVers = (name: KnownBrowserName): AgentReleaseInfo[] => {
const agent = findAgent(name)
// const agentVersion = getAgentSemVers(name).find(({ semver }) => semVerMatches(needle, semver))
// const agentRelease = agentVersion ? { ...agentVersion, released_on: epochToDate(agentVersion.release_date) } : null

return agent?.version_list ? agent.version_list.map(toAgentReleaseInfo) : []
}
// if (!agentRelease) {
// console.warn(`Unknown release ${name} ${version}`)
// }

const semVerMatches = (needle: SemVer, haystack: SemVer | SemVerRange | null): boolean => {
if (!haystack) return false

if (semver.isRange(haystack)) return semver.satisfies(needle, haystack)
return semver.equals(needle, haystack)
}

export const getAgentReleaseInfo = (name: KnownBrowserName, version: VersionStr): AgentReleaseInfo | null => {
const needle = toSemVer(version) as SemVer

const agentVersion = getAgentSemVers(name).find(({ semver }) => semVerMatches(needle, semver))
const agentRelease = agentVersion ? { ...agentVersion, released_on: epochToDate(agentVersion.release_date) } : null

if (!agentRelease) {
console.warn(`Unknown release ${name} ${version}`)
}

return agentRelease
}
// return agentRelease
// }
18 changes: 0 additions & 18 deletions lib/browsers_test.ts

This file was deleted.

42 changes: 42 additions & 0 deletions lib/caniuse_lite.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { agents } from 'caniuse-lite'

export type EpochDate = number
export type MajorVersionStr = string
export type UsageFloat = number

export type AgentLiteKey =
| 'and_chr'
| 'and_ff'
| 'and_qq'
| 'and_uc'
| 'android'
| 'baidu'
| 'bb'
| 'chrome'
| 'edge'
| 'firefox'
| 'ie_mob'
| 'ie'
| 'ios_saf'
| 'kaios'
| 'op_mini'
| 'op_mob'
| 'opera'
| 'safari'
| 'samsung'

export type AgentLiteStats = {
usage_global: Record<MajorVersionStr, UsageFloat>
prefix: string
versions: MajorVersionStr[]
browser: string
release_date: Record<MajorVersionStr, EpochDate>
}

export type CanIUseLite = {
agents: Record<AgentLiteKey, AgentLiteStats>
}

const data: CanIUseLite = { agents }

export default data
23 changes: 23 additions & 0 deletions lib/semver.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import * as semver from '@std/semver'
import type { Range as SemVerRange, SemVer } from '@std/semver'

import { countLiterals } from './utils.ts'

export const fuzzyParseSemVer = (version: string): SemVer | SemVerRange => {
if (semver.tryParse(version)) return semver.parse(version)

// If it looks like a range, then let's try to parse both sides loosely
if (countLiterals(version, '-') === 1) {
const minmax = version.split('-').map(fuzzyParseSemVer)
if (minmax.every(semver.isSemVer)) {
return semver.parseRange(minmax.map(semver.format).join('-'))
}
}

version = version.replace(/\.+$/, '')
while (!semver.tryParse(version) && countLiterals(version, '.') <= 2) {
version += '.0'
}

return semver.parse(version)
}
18 changes: 18 additions & 0 deletions lib/semver_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { assertEquals } from 'jsr:@std/assert'
import { parse, parseRange } from '@std/semver'
import { fuzzyParseSemVer } from './semver.ts'

Deno.test('fuzzyParseSemVer converts partial version strings to full semver', () => {
assertEquals(fuzzyParseSemVer('5'), parse('5.0.0'))
assertEquals(fuzzyParseSemVer('5.'), parse('5.0.0'))

assertEquals(fuzzyParseSemVer('5.5'), parse('5.5.0'))
assertEquals(fuzzyParseSemVer('5.5.'), parse('5.5.0'))

assertEquals(fuzzyParseSemVer('5.0.0-alpha'), parse('5.0.0-alpha'))
})

Deno.test('fuzzyParseSemVer converts partial ranges', () => {
assertEquals(fuzzyParseSemVer('5.0-5.0'), parseRange('5.0.0-5.0.0'))
assertEquals(fuzzyParseSemVer('8.1-8.4'), parseRange('8.1.0-8.4.0'))
})
5 changes: 5 additions & 0 deletions lib/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Convert an epoch timestamp to a Date
export const epochToDate = (epoch: number): Date => new Date(epoch * 1000)

// Count the number of occurrences of a character in a string
export const countLiterals = (str: string, char: string): number => str.split(char).length - 1
Loading

0 comments on commit c0fe248

Please sign in to comment.