diff --git a/src/ua.js b/src/ua.js new file mode 100644 index 0000000..834b609 --- /dev/null +++ b/src/ua.js @@ -0,0 +1,51 @@ +// ───────────────────────────────────────────────────────────── +// src/ua.js — minimal User-Agent parser. +// +// Coarse-grained classification only: device_type / os / browser. +// We deliberately do NOT pull in ua-parser-js — the project keeps a +// small dependency footprint and we only need three buckets. The raw +// UA is also stored alongside parsed fields (see src/events.js) so a +// regex miss can be re-classified later. +// +// Also owns MOBILE_UA_RE — the existing /timeline view-dispatch +// regex used by server.js. Single source of truth. +// ───────────────────────────────────────────────────────────── + +// UA substrings that mean "phone-class small screen". Tablets (iPad, +// Android tablets) deliberately do NOT match — they get the desktop +// view, which matches existing behaviour in server.js. +export const MOBILE_UA_RE = + /\b(iPhone|iPod|Android.*Mobile|Mobile.*Firefox|IEMobile|BlackBerry|Opera Mini)\b/i; + +// Tablet-class devices. Order matters in parseUA(): tablet check runs +// before mobile so "iPad" doesn't accidentally fall through to desktop. +const TABLET_UA_RE = /\b(iPad|Android(?!.*Mobile))\b/i; + +export function parseUA(ua) { + if (!ua || typeof ua !== 'string') { + return { device_type: null, os: null, browser: null }; + } + + // device_type + let device_type = 'desktop'; + if (TABLET_UA_RE.test(ua)) device_type = 'tablet'; + else if (MOBILE_UA_RE.test(ua)) device_type = 'mobile'; + + // os + let os = 'other'; + if (/\b(iPhone|iPad|iPod)\b/.test(ua)) os = 'iOS'; + else if (/\bAndroid\b/.test(ua)) os = 'Android'; + else if (/\bWindows\b/.test(ua)) os = 'Windows'; + else if (/Mac OS X|Macintosh/.test(ua)) os = 'macOS'; + else if (/\bLinux\b/.test(ua)) os = 'Linux'; + + // browser — order matters. All Chromium UAs include "Safari/", all + // Edge UAs include "Chrome/", so the most specific token must win. + let browser = 'other'; + if (/\bEdg\//.test(ua)) browser = 'Edge'; + else if (/\bFirefox\//.test(ua)) browser = 'Firefox'; + else if (/\bChrome\//.test(ua)) browser = 'Chrome'; + else if (/\bSafari\//.test(ua)) browser = 'Safari'; + + return { device_type, os, browser }; +}