162 lines
4.9 KiB
TypeScript
162 lines
4.9 KiB
TypeScript
import type { H3Event } from 'h3'
|
|
import { parseAcceptLanguage } from 'intl-parse-accept-language'
|
|
import { UAParser } from 'ua-parser-js'
|
|
import {
|
|
CLIs,
|
|
Crawlers,
|
|
Emails,
|
|
ExtraDevices,
|
|
Fetchers,
|
|
InApps,
|
|
MediaPlayers,
|
|
Vehicles,
|
|
} from 'ua-parser-js/extensions'
|
|
import { parseURL } from 'ufo'
|
|
import { getFlag } from '@/utils/flag'
|
|
|
|
function toBlobNumber(blob: string) {
|
|
return +blob.replace(/\D/g, '')
|
|
}
|
|
|
|
export const blobsMap = {
|
|
blob1: 'slug',
|
|
blob2: 'url',
|
|
blob3: 'ua',
|
|
blob4: 'ip',
|
|
blob5: 'referer',
|
|
blob6: 'country',
|
|
blob7: 'region',
|
|
blob8: 'city',
|
|
blob9: 'timezone',
|
|
blob10: 'language',
|
|
blob11: 'os',
|
|
blob12: 'browser',
|
|
blob13: 'browserType',
|
|
blob14: 'device',
|
|
blob15: 'deviceType',
|
|
blob16: 'COLO',
|
|
} as const
|
|
|
|
export const doublesMap = {
|
|
double1: 'latitude',
|
|
double2: 'longitude',
|
|
} as const
|
|
|
|
export type BlobsMap = typeof blobsMap
|
|
export type BlobsKey = keyof BlobsMap
|
|
|
|
export type DoublesMap = typeof doublesMap
|
|
export type DoublesKey = keyof DoublesMap
|
|
|
|
export type LogsKey = BlobsMap[BlobsKey] | DoublesMap[DoublesKey]
|
|
export type LogsMap = {
|
|
[key in BlobsMap[BlobsKey]]: string | undefined
|
|
} & {
|
|
[key in DoublesMap[DoublesKey]]?: number | undefined
|
|
}
|
|
|
|
export const logsMap = {
|
|
...Object.entries(blobsMap).reduce((acc, [k, v]) => ({ ...acc, [v]: k }), {}),
|
|
...Object.entries(doublesMap).reduce((acc, [k, v]) => ({ ...acc, [v]: k }), {}),
|
|
} as LogsMap
|
|
|
|
export function logs2blobs(logs: LogsMap) {
|
|
return (Object.keys(blobsMap) as BlobsKey[])
|
|
.sort((a, b) => toBlobNumber(a) - toBlobNumber(b))
|
|
.map(key => String(logs[blobsMap[key] as LogsKey] || ''))
|
|
}
|
|
|
|
export function blobs2logs(blobs: string[]) {
|
|
const logsList = Object.keys(blobsMap)
|
|
|
|
return blobs.reduce((logs, blob, i) => {
|
|
const key = blobsMap[logsList[i] as BlobsKey]
|
|
logs[key] = blob
|
|
return logs
|
|
}, {} as Partial<LogsMap>)
|
|
}
|
|
|
|
export function logs2doubles(logs: LogsMap) {
|
|
return (Object.keys(doublesMap) as DoublesKey[])
|
|
.sort((a, b) => toBlobNumber(a) - toBlobNumber(b))
|
|
.map(key => Number(logs[doublesMap[key] as LogsKey] || 0))
|
|
}
|
|
|
|
export function doubles2logs(doubles: number[]) {
|
|
const logsList = Object.keys(doublesMap)
|
|
|
|
return doubles.reduce((logs, double, i) => {
|
|
const key = doublesMap[logsList[i] as DoublesKey]
|
|
logs[key] = double
|
|
return logs
|
|
}, {} as Partial<LogsMap>)
|
|
}
|
|
|
|
export function useAccessLog(event: H3Event) {
|
|
const ip = getHeader(event, 'cf-connecting-ip') || getHeader(event, 'x-real-ip') || getRequestIP(event, { xForwardedFor: true })
|
|
|
|
const { host: referer } = parseURL(getHeader(event, 'referer'))
|
|
|
|
const acceptLanguage = getHeader(event, 'accept-language') || ''
|
|
const language = (parseAcceptLanguage(acceptLanguage) || [])[0]
|
|
|
|
const userAgent = getHeader(event, 'user-agent') || ''
|
|
const uaInfo = (new UAParser(userAgent, {
|
|
// eslint-disable-next-line ts/ban-ts-comment
|
|
// @ts-expect-error
|
|
browser: [Crawlers.browser || [], CLIs.browser || [], Emails.browser || [], Fetchers.browser || [], InApps.browser || [], MediaPlayers.browser || [], Vehicles.browser || []].flat(),
|
|
// eslint-disable-next-line ts/ban-ts-comment
|
|
// @ts-expect-error
|
|
device: [ExtraDevices.device || []].flat(),
|
|
})).getResult()
|
|
|
|
const { request: { cf } } = event.context.cloudflare
|
|
const link = event.context.link || {}
|
|
|
|
const isBot = cf?.botManagement?.verifiedBot
|
|
|| ['crawler', 'fetcher'].includes(uaInfo?.browser?.type || '')
|
|
|| ['spider', 'bot'].includes(uaInfo?.browser?.name?.toLowerCase() || '')
|
|
|
|
const { disableBotAccessLog } = useRuntimeConfig(event)
|
|
if (isBot && disableBotAccessLog) {
|
|
console.log('bot access log disabled:', userAgent)
|
|
return Promise.resolve()
|
|
}
|
|
|
|
const regionNames = new Intl.DisplayNames(['en'], { type: 'region' })
|
|
const countryName = regionNames.of(cf?.country || 'WD') // fallback to "Worldwide"
|
|
const accessLogs = {
|
|
url: link.url,
|
|
slug: link.slug,
|
|
ua: userAgent,
|
|
ip,
|
|
referer,
|
|
country: cf?.country,
|
|
region: `${getFlag(cf?.country)} ${[cf?.region, countryName].filter(Boolean).join(',')}`,
|
|
city: `${getFlag(cf?.country)} ${[cf?.city, countryName].filter(Boolean).join(',')}`,
|
|
timezone: cf?.timezone,
|
|
language,
|
|
os: uaInfo?.os?.name,
|
|
browser: uaInfo?.browser?.name,
|
|
browserType: uaInfo?.browser?.type,
|
|
device: uaInfo?.device?.model,
|
|
deviceType: uaInfo?.device?.type,
|
|
COLO: cf?.colo,
|
|
|
|
// For RealTime Globe
|
|
latitude: Number(cf?.latitude || getHeader(event, 'cf-iplatitude') || 0),
|
|
longitude: Number(cf?.longitude || getHeader(event, 'cf-iplongitude') || 0),
|
|
}
|
|
|
|
if (process.env.NODE_ENV === 'production') {
|
|
return hubAnalytics().put({
|
|
indexes: [link.id], // only one index
|
|
blobs: logs2blobs(accessLogs),
|
|
doubles: logs2doubles(accessLogs),
|
|
})
|
|
}
|
|
else {
|
|
console.log('access logs:', accessLogs, logs2blobs(accessLogs), logs2doubles(accessLogs), { ...blobs2logs(logs2blobs(accessLogs)), ...doubles2logs(logs2doubles(accessLogs)) })
|
|
return Promise.resolve()
|
|
}
|
|
}
|