{"version":3,"file":"user-agent.mjs","sources":["../src/user-agent.ts"],"sourcesContent":["/**\n * Minimal, dependency-free User-Agent parsing.\n *\n * This is intentionally heuristic and only aims to produce a human-readable label\n * (e.g. \"Chrome on macOS\") for displaying active sessions/devices. It is not a full\n * UA parser and should not be relied upon for feature detection.\n */\n\nexport interface ParsedUserAgent {\n browser?: string;\n os?: string;\n /** Human-readable label combining browser and OS, e.g. \"Chrome on macOS\". */\n deviceName?: string;\n}\n\n/**\n * Upper bound on the User-Agent length we are willing to scan. Real-world UA\n * strings are comfortably under this, and browser/OS tokens always appear early,\n * so clamping protects against pathologically large attacker-controlled headers\n * without losing accuracy for legitimate clients.\n */\nconst MAX_UA_LENGTH = 1024;\n\nconst detectBrowser = (ua: string): string | undefined => {\n // Order matters: more specific tokens must be checked first.\n if (/\\bEdg(?:e|A|iOS)?\\//.test(ua)) return 'Edge';\n if (/\\b(?:OPR|Opera)\\//.test(ua)) return 'Opera';\n if (/\\b(?:SamsungBrowser)\\//.test(ua)) return 'Samsung Internet';\n // Chromium forks below ship a \"Chrome/\" token too, so they must be matched\n // before the generic Chrome check or they'd all collapse into \"Chrome\".\n if (/\\bVivaldi\\//.test(ua)) return 'Vivaldi';\n if (/\\b(?:YaBrowser|Yowser)\\//.test(ua)) return 'Yandex Browser';\n if (/\\bBrave\\//.test(ua)) return 'Brave';\n if (/\\b(?:Firefox|FxiOS)\\//.test(ua)) return 'Firefox';\n if (/\\b(?:Chrome|CriOS|Chromium)\\//.test(ua)) return 'Chrome';\n // Safari ships \"Safari/xxx\" but so do Chrome/Edge; only treat as Safari when no Chrome token.\n if (/\\bVersion\\/[\\d.]+ (?:Mobile\\/\\S+ )?Safari\\//.test(ua) || /\\bSafari\\//.test(ua)) {\n return 'Safari';\n }\n return undefined;\n};\n\nconst detectOS = (ua: string): string | undefined => {\n if (/\\bWindows NT\\b/.test(ua)) return 'Windows';\n if (/\\b(?:iPhone|iPad|iPod)\\b/.test(ua)) return 'iOS';\n if (/\\bAndroid\\b/.test(ua)) return 'Android';\n // \"Mac OS X\" appears on iOS too, so this must come after the iOS check.\n if (/\\b(?:Macintosh|Mac OS X)\\b/.test(ua)) return 'macOS';\n if (/\\bCrOS\\b/.test(ua)) return 'ChromeOS';\n if (/\\bLinux\\b/.test(ua)) return 'Linux';\n return undefined;\n};\n\n/**\n * Parses a User-Agent string into a best-effort browser/OS and a display label.\n * Returns an empty object when the input is missing or unrecognized.\n */\nexport const parseUserAgent = (userAgent?: string | null): ParsedUserAgent => {\n if (!userAgent || typeof userAgent !== 'string') {\n return {};\n }\n\n // Clamp before scanning so an oversized header cannot drive the regex work.\n const ua = userAgent.length > MAX_UA_LENGTH ? userAgent.slice(0, MAX_UA_LENGTH) : userAgent;\n\n const browser = detectBrowser(ua);\n const os = detectOS(ua);\n\n let deviceName: string | undefined;\n if (browser && os) {\n deviceName = `${browser} on ${os}`;\n } else if (browser) {\n deviceName = browser;\n } else if (os) {\n deviceName = os;\n }\n\n return { browser, os, deviceName };\n};\n\n/**\n * Convenience helper returning only the human-readable device label, or undefined.\n */\nexport const getDeviceName = (userAgent?: string | null): string | undefined =>\n parseUserAgent(userAgent).deviceName;\n"],"names":["MAX_UA_LENGTH","detectBrowser","ua","test","undefined","detectOS","parseUserAgent","userAgent","length","slice","browser","os","deviceName","getDeviceName"],"mappings":"AAAA;;;;;;;;;;;AAoBC,IACD,MAAMA,aAAAA,GAAgB,IAAA;AAEtB,MAAMC,gBAAgB,CAACC,EAAAA,GAAAA;;AAErB,IAAA,IAAI,qBAAA,CAAsBC,IAAI,CAACD,EAAAA,CAAAA,EAAK,OAAO,MAAA;AAC3C,IAAA,IAAI,mBAAA,CAAoBC,IAAI,CAACD,EAAAA,CAAAA,EAAK,OAAO,OAAA;AACzC,IAAA,IAAI,wBAAA,CAAyBC,IAAI,CAACD,EAAAA,CAAAA,EAAK,OAAO,kBAAA;;;AAG9C,IAAA,IAAI,aAAA,CAAcC,IAAI,CAACD,EAAAA,CAAAA,EAAK,OAAO,SAAA;AACnC,IAAA,IAAI,0BAAA,CAA2BC,IAAI,CAACD,EAAAA,CAAAA,EAAK,OAAO,gBAAA;AAChD,IAAA,IAAI,WAAA,CAAYC,IAAI,CAACD,EAAAA,CAAAA,EAAK,OAAO,OAAA;AACjC,IAAA,IAAI,uBAAA,CAAwBC,IAAI,CAACD,EAAAA,CAAAA,EAAK,OAAO,SAAA;AAC7C,IAAA,IAAI,+BAAA,CAAgCC,IAAI,CAACD,EAAAA,CAAAA,EAAK,OAAO,QAAA;;AAErD,IAAA,IAAI,8CAA8CC,IAAI,CAACD,OAAO,YAAA,CAAaC,IAAI,CAACD,EAAAA,CAAAA,EAAK;QACnF,OAAO,QAAA;AACT,IAAA;IACA,OAAOE,SAAAA;AACT,CAAA;AAEA,MAAMC,WAAW,CAACH,EAAAA,GAAAA;AAChB,IAAA,IAAI,gBAAA,CAAiBC,IAAI,CAACD,EAAAA,CAAAA,EAAK,OAAO,SAAA;AACtC,IAAA,IAAI,0BAAA,CAA2BC,IAAI,CAACD,EAAAA,CAAAA,EAAK,OAAO,KAAA;AAChD,IAAA,IAAI,aAAA,CAAcC,IAAI,CAACD,EAAAA,CAAAA,EAAK,OAAO,SAAA;;AAEnC,IAAA,IAAI,4BAAA,CAA6BC,IAAI,CAACD,EAAAA,CAAAA,EAAK,OAAO,OAAA;AAClD,IAAA,IAAI,UAAA,CAAWC,IAAI,CAACD,EAAAA,CAAAA,EAAK,OAAO,UAAA;AAChC,IAAA,IAAI,WAAA,CAAYC,IAAI,CAACD,EAAAA,CAAAA,EAAK,OAAO,OAAA;IACjC,OAAOE,SAAAA;AACT,CAAA;AAEA;;;IAIO,MAAME,cAAAA,GAAiB,CAACC,SAAAA,GAAAA;AAC7B,IAAA,IAAI,CAACA,SAAAA,IAAa,OAAOA,SAAAA,KAAc,QAAA,EAAU;AAC/C,QAAA,OAAO,EAAC;AACV,IAAA;;IAGA,MAAML,EAAAA,GAAKK,UAAUC,MAAM,GAAGR,gBAAgBO,SAAAA,CAAUE,KAAK,CAAC,CAAA,EAAGT,aAAAA,CAAAA,GAAiBO,SAAAA;AAElF,IAAA,MAAMG,UAAUT,aAAAA,CAAcC,EAAAA,CAAAA;AAC9B,IAAA,MAAMS,KAAKN,QAAAA,CAASH,EAAAA,CAAAA;IAEpB,IAAIU,UAAAA;AACJ,IAAA,IAAIF,WAAWC,EAAAA,EAAI;AACjBC,QAAAA,UAAAA,GAAa,CAAA,EAAGF,OAAAA,CAAQ,IAAI,EAAEC,EAAAA,CAAAA,CAAI;AACpC,IAAA,CAAA,MAAO,IAAID,OAAAA,EAAS;QAClBE,UAAAA,GAAaF,OAAAA;AACf,IAAA,CAAA,MAAO,IAAIC,EAAAA,EAAI;QACbC,UAAAA,GAAaD,EAAAA;AACf,IAAA;IAEA,OAAO;AAAED,QAAAA,OAAAA;AAASC,QAAAA,EAAAA;AAAIC,QAAAA;AAAW,KAAA;AACnC;AAEA;;UAGaC,aAAAA,GAAgB,CAACN,YAC5BD,cAAAA,CAAeC,SAAAA,CAAAA,CAAWK;;;;"}