{ "version": 3, "sources": ["../../node_modules/.pnpm/device-detector-js@3.0.3/node_modules/device-detector-js/dist/utils/trim.js", "../../node_modules/.pnpm/device-detector-js@3.0.3/node_modules/device-detector-js/dist/utils/version.js", "../../node_modules/.pnpm/device-detector-js@3.0.3/node_modules/device-detector-js/dist/utils/variable-replacement.js", "../../node_modules/.pnpm/device-detector-js@3.0.3/node_modules/device-detector-js/dist/utils/memory-cache.js", "../../node_modules/.pnpm/device-detector-js@3.0.3/node_modules/device-detector-js/dist/utils/user-agent.js", "../../node_modules/.pnpm/device-detector-js@3.0.3/node_modules/device-detector-js/dist/fixtures/regexes/client/browsers.json", "../../node_modules/.pnpm/device-detector-js@3.0.3/node_modules/device-detector-js/dist/fixtures/regexes/client/browser_engine.json", "../../node_modules/.pnpm/device-detector-js@3.0.3/node_modules/device-detector-js/dist/parsers/client/fixtures/available-browsers.json", "../../node_modules/.pnpm/device-detector-js@3.0.3/node_modules/device-detector-js/dist/parsers/client/fixtures/mobile-only-browsers.json", "../../node_modules/.pnpm/device-detector-js@3.0.3/node_modules/device-detector-js/dist/parsers/client/browser.js", "../../node_modules/.pnpm/device-detector-js@3.0.3/node_modules/device-detector-js/dist/fixtures/regexes/client/mobile_apps.json", "../../node_modules/.pnpm/device-detector-js@3.0.3/node_modules/device-detector-js/dist/parsers/client/mobile-apps.js", "../../node_modules/.pnpm/device-detector-js@3.0.3/node_modules/device-detector-js/dist/fixtures/regexes/client/feed_readers.json", "../../node_modules/.pnpm/device-detector-js@3.0.3/node_modules/device-detector-js/dist/parsers/client/feed-readers.js", "../../node_modules/.pnpm/device-detector-js@3.0.3/node_modules/device-detector-js/dist/fixtures/regexes/client/libraries.json", "../../node_modules/.pnpm/device-detector-js@3.0.3/node_modules/device-detector-js/dist/parsers/client/libraries.js", "../../node_modules/.pnpm/device-detector-js@3.0.3/node_modules/device-detector-js/dist/fixtures/regexes/client/mediaplayers.json", "../../node_modules/.pnpm/device-detector-js@3.0.3/node_modules/device-detector-js/dist/parsers/client/media-players.js", "../../node_modules/.pnpm/device-detector-js@3.0.3/node_modules/device-detector-js/dist/fixtures/regexes/client/pim.json", "../../node_modules/.pnpm/device-detector-js@3.0.3/node_modules/device-detector-js/dist/parsers/client/personal-information-managers.js", "../../node_modules/.pnpm/device-detector-js@3.0.3/node_modules/device-detector-js/dist/parsers/client/index.js", "../../node_modules/.pnpm/device-detector-js@3.0.3/node_modules/device-detector-js/dist/fixtures/regexes/device/cameras.json", "../../node_modules/.pnpm/device-detector-js@3.0.3/node_modules/device-detector-js/dist/parsers/device/cameras.js", "../../node_modules/.pnpm/device-detector-js@3.0.3/node_modules/device-detector-js/dist/fixtures/regexes/device/mobiles.json", "../../node_modules/.pnpm/device-detector-js@3.0.3/node_modules/device-detector-js/dist/utils/model.js", "../../node_modules/.pnpm/device-detector-js@3.0.3/node_modules/device-detector-js/dist/parsers/device/mobiles.js", "../../node_modules/.pnpm/device-detector-js@3.0.3/node_modules/device-detector-js/dist/fixtures/regexes/device/televisions.json", "../../node_modules/.pnpm/device-detector-js@3.0.3/node_modules/device-detector-js/dist/parsers/device/televisions.js", "../../node_modules/.pnpm/device-detector-js@3.0.3/node_modules/device-detector-js/dist/fixtures/regexes/device/car_browsers.json", "../../node_modules/.pnpm/device-detector-js@3.0.3/node_modules/device-detector-js/dist/parsers/device/cars.js", "../../node_modules/.pnpm/device-detector-js@3.0.3/node_modules/device-detector-js/dist/fixtures/regexes/device/consoles.json", "../../node_modules/.pnpm/device-detector-js@3.0.3/node_modules/device-detector-js/dist/parsers/device/consoles.js", "../../node_modules/.pnpm/device-detector-js@3.0.3/node_modules/device-detector-js/dist/fixtures/regexes/device/notebooks.json", "../../node_modules/.pnpm/device-detector-js@3.0.3/node_modules/device-detector-js/dist/parsers/device/notebooks.js", "../../node_modules/.pnpm/device-detector-js@3.0.3/node_modules/device-detector-js/dist/fixtures/regexes/device/portable_media_player.json", "../../node_modules/.pnpm/device-detector-js@3.0.3/node_modules/device-detector-js/dist/parsers/device/portable-media-players.js", "../../node_modules/.pnpm/device-detector-js@3.0.3/node_modules/device-detector-js/dist/parsers/device/index.js", "../../node_modules/.pnpm/device-detector-js@3.0.3/node_modules/device-detector-js/dist/fixtures/regexes/oss.json", "../../node_modules/.pnpm/device-detector-js@3.0.3/node_modules/device-detector-js/dist/parsers/operating-system/fixtures/operating-system.json", "../../node_modules/.pnpm/device-detector-js@3.0.3/node_modules/device-detector-js/dist/parsers/operating-system/index.js", "../../node_modules/.pnpm/device-detector-js@3.0.3/node_modules/device-detector-js/dist/fixtures/regexes/vendorfragments.json", "../../node_modules/.pnpm/device-detector-js@3.0.3/node_modules/device-detector-js/dist/parsers/vendor-fragment/index.js", "../../node_modules/.pnpm/device-detector-js@3.0.3/node_modules/device-detector-js/dist/fixtures/regexes/bots.json", "../../node_modules/.pnpm/device-detector-js@3.0.3/node_modules/device-detector-js/dist/parsers/bot/index.js", "../../node_modules/.pnpm/device-detector-js@3.0.3/node_modules/device-detector-js/dist/utils/version-compare.js", "../../node_modules/.pnpm/device-detector-js@3.0.3/node_modules/device-detector-js/dist/index.js", "../../node_modules/.pnpm/stackframe@1.3.4/node_modules/stackframe/stackframe.js", "../../node_modules/.pnpm/error-stack-parser@2.1.4/node_modules/error-stack-parser/error-stack-parser.js", "../../node_modules/.pnpm/cross-fetch@3.1.8/node_modules/cross-fetch/dist/browser-ponyfill.js", "../../node_modules/.pnpm/bintrees@1.0.2/node_modules/bintrees/lib/treebase.js", "../../node_modules/.pnpm/bintrees@1.0.2/node_modules/bintrees/lib/rbtree.js", "../../node_modules/.pnpm/bintrees@1.0.2/node_modules/bintrees/lib/bintree.js", "../../node_modules/.pnpm/bintrees@1.0.2/node_modules/bintrees/index.js", "../../node_modules/.pnpm/tdigest@0.1.2/node_modules/tdigest/tdigest.js", "../../_packs/entry.js", "../../node_modules/.pnpm/promise-polyfill@8.3.0/node_modules/promise-polyfill/src/finally.js", "../../node_modules/.pnpm/promise-polyfill@8.3.0/node_modules/promise-polyfill/src/allSettled.js", "../../node_modules/.pnpm/promise-polyfill@8.3.0/node_modules/promise-polyfill/src/any.js", "../../node_modules/.pnpm/promise-polyfill@8.3.0/node_modules/promise-polyfill/src/index.js", "../../node_modules/.pnpm/@airbrake+browser@1.4.2/node_modules/@airbrake/browser/src/jsonify_notice.ts", "../../node_modules/.pnpm/@airbrake+browser@1.4.2/node_modules/@airbrake/browser/src/metrics.ts", "../../node_modules/.pnpm/@airbrake+browser@1.4.2/node_modules/@airbrake/browser/src/scope.ts", "../../node_modules/.pnpm/@airbrake+browser@1.4.2/node_modules/@airbrake/browser/src/processor/esp.ts", "../../node_modules/.pnpm/@airbrake+browser@1.4.2/node_modules/@airbrake/browser/src/filter/angular_message.ts", "../../node_modules/.pnpm/@airbrake+browser@1.4.2/node_modules/@airbrake/browser/src/filter/debounce.ts", "../../node_modules/.pnpm/@airbrake+browser@1.4.2/node_modules/@airbrake/browser/src/filter/ignore_noise.ts", "../../node_modules/.pnpm/@airbrake+browser@1.4.2/node_modules/@airbrake/browser/src/filter/uncaught_message.ts", "../../node_modules/.pnpm/@airbrake+browser@1.4.2/node_modules/@airbrake/browser/src/http_req/fetch.ts", "../../node_modules/.pnpm/@airbrake+browser@1.4.2/node_modules/@airbrake/browser/src/http_req/api.ts", "../../node_modules/.pnpm/@airbrake+browser@1.4.2/node_modules/@airbrake/browser/src/http_req/node.ts", "../../node_modules/.pnpm/@airbrake+browser@1.4.2/node_modules/@airbrake/browser/src/http_req/index.ts", "../../node_modules/.pnpm/@airbrake+browser@1.4.2/node_modules/@airbrake/browser/src/tdshared.ts", "../../node_modules/.pnpm/@airbrake+browser@1.4.2/node_modules/@airbrake/browser/src/queries.ts", "../../node_modules/.pnpm/@airbrake+browser@1.4.2/node_modules/@airbrake/browser/src/queues.ts", "../../node_modules/.pnpm/@airbrake+browser@1.4.2/node_modules/@airbrake/browser/src/routes.ts", "../../node_modules/.pnpm/@airbrake+browser@1.4.2/node_modules/@airbrake/browser/src/version.ts", "../../node_modules/.pnpm/@airbrake+browser@1.4.2/node_modules/@airbrake/browser/src/base_notifier.ts", "../../node_modules/.pnpm/@airbrake+browser@1.4.2/node_modules/@airbrake/browser/src/filter/window.ts", "../../node_modules/.pnpm/@airbrake+browser@1.4.2/node_modules/@airbrake/browser/src/instrumentation/console.ts", "../../node_modules/.pnpm/@airbrake+browser@1.4.2/node_modules/@airbrake/browser/src/instrumentation/dom.ts", "../../node_modules/.pnpm/@airbrake+browser@1.4.2/node_modules/@airbrake/browser/src/instrumentation/fetch.ts", "../../node_modules/.pnpm/@airbrake+browser@1.4.2/node_modules/@airbrake/browser/src/instrumentation/location.ts", "../../node_modules/.pnpm/@airbrake+browser@1.4.2/node_modules/@airbrake/browser/src/instrumentation/xhr.ts", "../../node_modules/.pnpm/@airbrake+browser@1.4.2/node_modules/@airbrake/browser/src/notifier.ts", "../../node_modules/.pnpm/@hotwired+turbo@7.3.0/node_modules/@hotwired/turbo/dist/turbo.es2017-esm.js", "../../node_modules/.pnpm/@hotwired+stimulus@3.2.2/node_modules/@hotwired/stimulus/dist/stimulus.js", "../../_packs/controllers/body_scroll_controller.js", "../../_packs/controllers/device_detector_controller.js", "../../_packs/controllers/button_copy_controller.js", "../../_packs/controllers/footnotes_controller.js", "../../_packs/controllers/support_us_controller.js", "../../_packs/controllers/modal_controller.js", "../../_packs/controllers/authorize_interaction_controller.js", "../../_packs/controllers/tabs_controller.js"], "sourcesContent": ["\"use strict\";\nObject.defineProperty(exports, \"__esModule\", { value: true });\nexports.trim = (str, char) => {\n return str.replace(new RegExp(\"^[\" + char + \"]+|[\" + char + \"]+$\", \"g\"), \"\");\n};\n", "\"use strict\";\nObject.defineProperty(exports, \"__esModule\", { value: true });\nconst trim_1 = require(\"./trim\");\nexports.formatVersion = (version, versionTruncation) => {\n if (version === undefined)\n return \"\";\n const versionString = trim_1.trim(version, \". \").replace(new RegExp(\"_\", \"g\"), \".\");\n const versionParts = versionString.split(\".\");\n // Return if the string is not only digits once we removed the dots\n if (!/^\\d+$/.test(versionParts.join(\"\"))) {\n return versionString;\n }\n if (versionTruncation !== 0) {\n if (Number.isInteger(parseFloat(versionString))) {\n return parseInt(versionString, 10).toFixed(1);\n }\n }\n if (versionParts.length > 1) {\n if (versionTruncation !== null) {\n return versionParts.slice(0, versionTruncation + 1).join(\".\");\n }\n }\n return versionString;\n};\nexports.parseBrowserEngineVersion = (userAgent, engine) => {\n if (!engine)\n return \"\";\n if (engine === \"Gecko\") {\n const geckoVersionRegex = /[ ](?:rv[: ]([0-9\\.]+)).*gecko\\/[0-9]{8,10}/i;\n const match = userAgent.match(geckoVersionRegex);\n if (match) {\n return match.pop();\n }\n }\n const regex = new RegExp(`${engine}\\\\s*\\\\/?\\\\s*((?:(?=\\\\d+\\\\.\\\\d)\\\\d+[.\\\\d]*|\\\\d{1,7}(?=(?:\\\\D|$))))`, \"i\");\n const match = userAgent.match(regex);\n if (!match)\n return \"\";\n return match.pop();\n};\n", "\"use strict\";\nObject.defineProperty(exports, \"__esModule\", { value: true });\nexports.variableReplacement = (template, variables) => {\n const regex = new RegExp(`\\\\$\\\\d`, \"g\");\n if (template === null || template === undefined)\n return \"\";\n return template.replace(regex, (match) => {\n const index = parseInt(match.substr(1), 10);\n const variable = variables[index - 1];\n return variable || \"\";\n });\n};\n", "\"use strict\";\nObject.defineProperty(exports, \"__esModule\", { value: true });\nexports.memoryCache = () => {\n const memoryCacheBucket = {};\n const set = (key, value) => {\n memoryCacheBucket[key] = value;\n };\n const get = (key) => {\n if (memoryCacheBucket.hasOwnProperty(key)) {\n return memoryCacheBucket[key];\n }\n };\n return {\n set,\n get\n };\n};\n", "\"use strict\";\nObject.defineProperty(exports, \"__esModule\", { value: true });\nconst memory_cache_1 = require(\"./memory-cache\");\nconst cache = memory_cache_1.memoryCache();\nconst getRegexInstance = (rawRegex) => {\n const cachedRegexInstance = cache.get(rawRegex);\n if (cachedRegexInstance)\n return cachedRegexInstance.value;\n const regexInstance = RegExp(`(?:^|[^A-Z0-9\\-_]|[^A-Z0-9\\-]_|sprd-)(?:${rawRegex})`, \"i\");\n cache.set(rawRegex, {\n value: regexInstance\n });\n return regexInstance;\n};\nexports.userAgentParser = (rawRegex, userAgent) => {\n // TODO: find out why it fails in some browsers\n try {\n const regexInstance = getRegexInstance(rawRegex);\n const match = regexInstance.exec(userAgent);\n return match ? match.slice(1) : null;\n }\n catch (_a) {\n return null;\n }\n};\n", "[\n {\n \"regex\": \"Helio/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"Helio\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Blink\"\n }\n },\n {\n \"regex\": \"7654Browser/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"7654 Browser\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Blink\"\n }\n },\n {\n \"regex\": \"Qazweb/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"Qazweb\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Gecko\"\n }\n },\n {\n \"regex\": \"Degdegan/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"deg-degan\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Blink\"\n }\n },\n {\n \"regex\": \"JavaFX/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"JavaFX\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"WebKit\"\n }\n },\n {\n \"regex\": \"Chedot/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"Chedot\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Blink\"\n }\n },\n {\n \"regex\": \"Chrome/(\\\\d+[\\\\.\\\\d]+) \\\\(Chromium GOST\\\\)\",\n \"name\": \"Chromium GOST\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Blink\"\n }\n },\n {\n \"regex\": \"(?:DeledaoPersonal|DeledaoFamily)/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"Deledao\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Blink\"\n }\n },\n {\n \"regex\": \"HasBrowser/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"HasBrowser\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Blink\"\n }\n },\n {\n \"regex\": \"Byffox/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"Byffox\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Gecko\"\n }\n },\n {\n \"regex\": \"Chrome/(\\\\d+[\\\\.\\\\d]+).+AgentWeb.+UCBrowser\",\n \"name\": \"CoolBrowser\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Blink\"\n }\n },\n {\n \"regex\": \"DotBrowser/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"Dot Browser\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Blink\"\n }\n },\n {\n \"regex\": \"CravingExplorer/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"Craving Explorer\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Blink\"\n }\n },\n {\n \"regex\": \"DeskBrowse/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"DeskBrowse\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Lolifox/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"Lolifox\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"qutebrowser/(\\\\d+[\\\\.\\\\d]+).+Chrome\",\n \"name\": \"Qutebrowser\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Blink\"\n }\n },\n {\n \"regex\": \"qutebrowser(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Qutebrowser\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"flast/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"Flast\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Blink\"\n }\n },\n {\n \"regex\": \"PolyBrowser/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"PolyBrowser\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Gecko\"\n }\n },\n {\n \"regex\": \"Chrome.+BriskBard/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"BriskBard\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Blink\"\n }\n },\n {\n \"regex\": \"BriskBard(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"BriskBard\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"GinxDroidBrowser/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"GinxDroid Browser\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Blink\"\n }\n },\n {\n \"regex\": \"AviraScout/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"Avira Scout\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Blink\"\n }\n },\n {\n \"regex\": \"VenusBrowser/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"Venus Browser\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Blink\"\n }\n },\n {\n \"regex\": \"Chrome.+Otter(?:[ /](\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Otter Browser\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Blink\"\n }\n },\n {\n \"regex\": \"Otter(?:[ /](\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Otter Browser\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Chrome.+Smooz/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"Smooz\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Blink\"\n }\n },\n {\n \"regex\": \"Smooz/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"Smooz\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Cornowser/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"Cornowser\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Blink\"\n }\n },\n {\n \"regex\": \"Orca/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"Orca\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Flow/(?:(\\\\d+[\\\\.\\\\d]+))\",\n \"name\": \"Flow\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"EkiohFlow\"\n }\n },\n {\n \"regex\": \"Ekioh/(?:(\\\\d+[\\\\.\\\\d]+))\",\n \"name\": \"Flow\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"EkiohFlow\"\n }\n },\n {\n \"regex\": \"xStand/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"xStand\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"WebKit\"\n }\n },\n {\n \"regex\": \"Biyubi/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"Biyubi\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"(?:Perfect%20Browser(?:-iPad)?|Perfect(?:BrowserPro)?)/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"Perfect Browser\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"WebKit\"\n }\n },\n {\n \"regex\": \"Browser/Phantom/V(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"Phantom Browser\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"AwoX(?:/(\\\\d+[\\\\.\\\\d]+))? Browser\",\n \"name\": \"AwoX\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Zetakey/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"Zetakey\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"WebKit\"\n }\n },\n {\n \"regex\": \"PlayFreeBrowser/(?:(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"PlayFree Browser\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"(?:chimlac_browser|chimlac)/(?:(\\\\d+[\\\\.\\\\d]+))\",\n \"name\": \"Chim Lac\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Blink\"\n }\n },\n {\n \"regex\": \"Odin/(?:(\\\\d+[\\\\.\\\\d]+))\",\n \"name\": \"Odin\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Blink\"\n }\n },\n {\n \"regex\": \"Tbrowser/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"T-Browser\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Blink\"\n }\n },\n {\n \"regex\": \"SFive(?:_Android)?/.+ Chrome/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"SFive\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Blink\"\n }\n },\n {\n \"regex\": \"SFive_IOS/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"SFive\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Navigateur web/(?:(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Navigateur Web\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Sraf(?:[/ ](\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Seraphic Sraf\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Blink\"\n }\n },\n {\n \"regex\": \"SeewoBrowser/(?:(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Seewo Browser\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Blink\"\n }\n },\n {\n \"regex\": \"(?:Kode(?:iOS)?/(?:(\\\\d+[\\\\.\\\\d]+))?|TansoDL)\",\n \"name\": \"Kode Browser\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"UR/(?:(\\\\d+[\\\\.\\\\d]+))\",\n \"name\": \"UR Browser\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Blink\"\n }\n },\n {\n \"regex\": \"OceanHero/([\\\\.\\\\d]+)\",\n \"name\": \"OceanHero\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Blink\"\n }\n },\n {\n \"regex\": \"Chrome/.+ SLBrowser/(?:(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Smart Lenovo Browser\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Blink\"\n }\n },\n {\n \"regex\": \"SLBrowser/(?:(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Smart Lenovo Browser\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Browzar\",\n \"name\": \"Browzar\",\n \"version\": \"\"\n },\n {\n \"regex\": \"Stargon/(?:(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Stargon\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Blink\"\n }\n },\n {\n \"regex\": \"NFSBrowser/(?:(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"NFS Browser\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Blink\"\n }\n },\n {\n \"regex\": \"Borealis/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"Borealis Navigator\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"YoloBrowser/(?:(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Yolo Browser\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Blink\"\n }\n },\n {\n \"regex\": \"PHX/(?:(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Phoenix Browser\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"PrivacyWall/(?:(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"PrivacyWall\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Ghostery:?(\\\\d+[\\\\.\\\\d]+)?\",\n \"name\": \"Ghostery Privacy Browser\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Firefox/.*(?:Turkcell-)?YaaniBrowser(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Yaani Browser\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Gecko\"\n }\n },\n {\n \"regex\": \"(?:Turkcell-)?YaaniBrowser(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Yaani Browser\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Blink\"\n }\n },\n {\n \"regex\": \"SEB/(?:(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Safe Exam Browser\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Colibri/(?:(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Colibri\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Xvast/(?:(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Xvast\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"TungstenBrowser/(?:(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Tungsten\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Lulumi-browser/(?:(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Lulumi\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"ybrowser/(?:(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Yahoo! Japan Browser\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"iLunascapeLite/(?:(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Lunascape Lite\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Polypane/(?:(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Polypane\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"OhHaiBrowser/(?:(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"OhHai Browser\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Sizzy/(?:(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Sizzy\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"GlassBrowser/(?:(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Glass Browser\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"ToGate/(?:(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"ToGate\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"AirWatch Browser v(?:(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"VMware AirWatch\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"ADG/(?:(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"AOL Desktop\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Elements Browser/(?:(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Elements Browser\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Light/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"Light\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Valve Steam GameOverlay/(?:(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Steam In-Game Overlay\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"115Browser/(?:(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"115 Browser\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Atom/(?:(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Atom\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Mobile VR.+Firefox\",\n \"name\": \"Firefox Reality\",\n \"version\": \"\"\n },\n {\n \"regex\": \"AVG(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"AVG Secure Browser\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Start/(?:(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"START Internet Browser\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Lovense(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Lovense Browser\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"com.airfind.deltabrowser(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Delta Browser\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"(?:Ordissimo|webissimo3)(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Ordissimo\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"CCleaner(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"CCleaner\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"AlohaLite(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Aloha Browser Lite\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Blink\"\n }\n },\n {\n \"regex\": \"TaoBrowser(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Tao Browser\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Falkon(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Falkon\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"mCent(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"mCent\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"SalamWeb(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"SalamWeb\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"BlackHawk(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"BlackHawk\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Minimo(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Minimo\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"WIB(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Wear Internet Browser\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Origyn Web Browser\",\n \"name\": \"Origyn Web Browser\",\n \"version\": \"\"\n },\n {\n \"regex\": \"Kinza(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Kinza\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Beamrise(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Beamrise\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Faux(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Faux Browser\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"splash Version(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Splash\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"MZBrowser(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Meizu Browser\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"COSBrowser(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"COS Browser\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Crusta(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Crusta\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Hawk/TurboBrowser(?:/v?(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Hawk Turbo Browser\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Blink\"\n }\n },\n {\n \"regex\": \"FreeU(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"FreeU\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Blink\"\n }\n },\n {\n \"regex\": \"NoxBrowser(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Nox Browser\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Blink\"\n }\n },\n {\n \"regex\": \"Basilisk(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Basilisk\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Goanna\"\n }\n },\n {\n \"regex\": \"SputnikBrowser(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Sputnik Browser\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Blink\"\n }\n },\n {\n \"regex\": \"TNSBrowser(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"K.Browser\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Blink\"\n }\n },\n {\n \"regex\": \"OculusBrowser(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Oculus Browser\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Jio(?:Browser|Pages)(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Jio Browser\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Blink\"\n }\n },\n {\n \"regex\": \"Chrome/.+ Hola(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"hola! Browser\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Blink\"\n }\n },\n {\n \"regex\": \"Slimjet/(?:(\\\\d+[\\\\.\\\\d]+))\",\n \"name\": \"Slimjet\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Blink\"\n }\n },\n {\n \"regex\": \"7Star/(?:(\\\\d+[\\\\.\\\\d]+))\",\n \"name\": \"7Star\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Blink\"\n }\n },\n {\n \"regex\": \"MxNitro/(?:(\\\\d+[\\\\.\\\\d]+))\",\n \"name\": \"MxNitro\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Blink\"\n }\n },\n {\n \"regex\": \"HuaweiBrowser(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Huawei Browser\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"VivoBrowser(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"vivo Browser\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"RealmeBrowser(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Realme Browser\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Beaker ?Browser(?:[/ ](\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Beaker Browser\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Blink\"\n }\n },\n {\n \"regex\": \"QwantiOS/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"Qwant Mobile\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"WebKit\"\n }\n },\n {\n \"regex\": \"QwantMobile(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Qwant Mobile\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Gecko\"\n }\n },\n {\n \"regex\": \"Qwant/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"Qwant Mobile\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Gecko\"\n }\n },\n {\n \"regex\": \"TenFourFox(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"TenFourFox\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Gecko\"\n }\n },\n {\n \"regex\": \"AOLShield(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"AOL Shield\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Gecko\"\n }\n },\n {\n \"regex\": \"Edge[ /](\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"Microsoft Edge\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Edge\"\n }\n },\n {\n \"regex\": \"EdgiOS[ /](\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"Microsoft Edge\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"WebKit\"\n }\n },\n {\n \"regex\": \"EdgA[ /](\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"Microsoft Edge\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Blink\"\n }\n },\n {\n \"regex\": \"Edg[ /](\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"Microsoft Edge\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Blink\"\n }\n },\n {\n \"regex\": \"QIHU 360[ES]E\",\n \"name\": \"360 Browser\",\n \"version\": \"\"\n },\n {\n \"regex\": \"360 Aphone Browser(?: \\\\((\\\\d+[\\\\.\\\\d]+)(?:beta)?\\\\))?\",\n \"name\": \"360 Phone Browser\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"WebKit\"\n }\n },\n {\n \"regex\": \"SailfishBrowser(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Sailfish Browser\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Gecko\"\n }\n },\n {\n \"regex\": \"IceCat(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"IceCat\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Gecko\"\n }\n },\n {\n \"regex\": \"Mobicip\",\n \"name\": \"Mobicip\",\n \"version\": \"\",\n \"engine\": {\n \"default\": \"Gecko\"\n }\n },\n {\n \"regex\": \"Camino(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Camino\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Gecko\"\n }\n },\n {\n \"regex\": \"Waterfox(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Waterfox\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Gecko\"\n }\n },\n {\n \"regex\": \"Chrome/.+ AlohaBrowser(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Aloha Browser\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Blink\"\n }\n },\n {\n \"regex\": \"AlohaBrowser(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Aloha Browser\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"(?:Avast|ASW|Safer)(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Avast Secure Browser\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Epic(?:/(\\\\d+[\\\\.\\\\d]+))\",\n \"name\": \"Epic\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Gecko\"\n }\n },\n {\n \"regex\": \"Fennec(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Fennec\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Gecko\"\n }\n },\n {\n \"regex\": \"Firefox.*Tablet browser (\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"MicroB\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Gecko\"\n }\n },\n {\n \"regex\": \"Maemo Browser(?: (\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"MicroB\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Gecko\"\n }\n },\n {\n \"regex\": \"Deepnet Explorer (\\\\d+[\\\\.\\\\d]+)?\",\n \"name\": \"Deepnet Explorer\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Avant ?Browser\",\n \"name\": \"Avant Browser\",\n \"version\": \"\",\n \"engine\": {\n \"default\": \"\"\n }\n },\n {\n \"regex\": \"OppoBrowser(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Oppo Browser\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Chrome/(\\\\d+[\\\\.\\\\d]+).*MRCHROME\",\n \"name\": \"Amigo\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"WebKit\",\n \"versions\": {\n \"28\": \"Blink\"\n }\n }\n },\n {\n \"regex\": \"AtomicBrowser(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Atomic Web Browser\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Bunjalloo(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Bunjalloo\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Brave(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Brave\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Blink\"\n }\n },\n {\n \"regex\": \"Iridium(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Iridium\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Iceweasel(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Iceweasel\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Gecko\"\n }\n },\n {\n \"regex\": \"WebPositive\",\n \"name\": \"WebPositive\",\n \"version\": \"\",\n \"engine\": {\n \"default\": \"WebKit\"\n }\n },\n {\n \"regex\": \".*Goanna.*PaleMoon(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Pale Moon\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Goanna\"\n }\n },\n {\n \"regex\": \"PaleMoon(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Pale Moon\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Gecko\"\n }\n },\n {\n \"regex\": \"CometBird(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"CometBird\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Gecko\"\n }\n },\n {\n \"regex\": \"IceDragon(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"IceDragon\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Gecko\"\n }\n },\n {\n \"regex\": \"Flock(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Flock\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Gecko\",\n \"versions\": {\n \"3\": \"WebKit\"\n }\n }\n },\n {\n \"regex\": \"JigBrowserPlus/(?:(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Jig Browser Plus\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"jig browser(?: web;|9i?)?(?:[/ ](\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Jig Browser\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Kapiko(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Kapiko\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Gecko\"\n }\n },\n {\n \"regex\": \"Kylo(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Kylo\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Gecko\"\n }\n },\n {\n \"regex\": \"Origin/(?:(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Origin In-Game Overlay\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Cunaguaro(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Cunaguaro\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"(?:TO-Browser/TOB|DT-Browser/DTB)(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"t-online.de Browser\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Kazehakase(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Kazehakase\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"\"\n }\n },\n {\n \"regex\": \"ArcticFox(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Arctic Fox\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Goanna\"\n }\n },\n {\n \"regex\": \"Mypal(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Mypal\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Goanna\"\n }\n },\n {\n \"regex\": \"Centaury(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Centaury\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Goanna\"\n }\n },\n {\n \"regex\": \"(?:Focus|Klar)(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Firefox Focus\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Cyberfox(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Cyberfox\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Gecko\"\n }\n },\n {\n \"regex\": \"Firefox/(\\\\d+[\\\\.\\\\d]+).*\\\\(Swiftfox\\\\)\",\n \"name\": \"Swiftfox\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Gecko\"\n }\n },\n {\n \"regex\": \"(?:Mobile|Tablet).*Servo.*Firefox(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Firefox Mobile\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Servo\"\n }\n },\n {\n \"regex\": \"(?:Mobile|Tablet).*Firefox(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Firefox Mobile\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Gecko\"\n }\n },\n {\n \"regex\": \"FxiOS/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"Firefox Mobile iOS\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"WebKit\"\n }\n },\n {\n \"regex\": \".*Servo.*Firefox(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Firefox\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Servo\"\n }\n },\n {\n \"regex\": \"(?!.*Opera[ /])Firefox(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Firefox\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Gecko\"\n }\n },\n {\n \"regex\": \"(?:BonEcho|GranParadiso|Lorentz|Minefield|Namoroka|Shiretoko)/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"Firefox\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Gecko\"\n }\n },\n {\n \"regex\": \"ANTFresco(?:[/ ](\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"ANT Fresco\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"ANTGalio(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"ANTGalio\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"(?:Espial|Escape)(?:[/ ](\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Espial TV Browser\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"RockMelt(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"RockMelt\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"WebKit\"\n }\n },\n {\n \"regex\": \"Fireweb Navigator(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Fireweb Navigator\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Fireweb(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Fireweb\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"(?:Navigator|Netscape6?)(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Netscape\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"\"\n }\n },\n {\n \"regex\": \"(?:Polarity)(?:[/ ](\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Polarity\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"(?:QupZilla)(?:[/ ](\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"QupZilla\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"(?:Dooble)(?:[/ ](\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Dooble\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Whale/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"Whale Browser\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Blink\"\n }\n },\n {\n \"regex\": \"Obigo[ ]?(?:InternetBrowser|Browser)?(?:[ /]([a-z0-9]*))?\",\n \"name\": \"Obigo\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Obigo|Teleca\",\n \"name\": \"Obigo\",\n \"version\": \"\"\n },\n {\n \"regex\": \"UCBrowserHD/(\\\\d[\\\\d\\\\.]+)\",\n \"name\": \"UC Browser HD\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"UCMini(?:[ /]?(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"UC Browser Mini\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"UC[ ]?Browser.* \\\\(UCMini\\\\)\",\n \"name\": \"UC Browser Mini\",\n \"version\": \"\"\n },\n {\n \"regex\": \"UCTurbo(?:[ /]?(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"UC Browser Turbo\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"UC[ ]?Browser.* \\\\(UCTurbo\\\\)\",\n \"name\": \"UC Browser Turbo\",\n \"version\": \"\"\n },\n {\n \"regex\": \"OPRGX(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Opera GX\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Blink\"\n }\n },\n {\n \"regex\": \"(?:Opera Tablet.*Version|Opera/.+Opera Mobi.+Version|Mobile.+OPR)/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"Opera Mobile\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Presto\",\n \"versions\": {\n \"15\": \"Blink\"\n }\n }\n },\n {\n \"regex\": \"MMS/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"Opera Neon\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Blink\"\n }\n },\n {\n \"regex\": \"OMI/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"Opera Devices\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Blink\"\n }\n },\n {\n \"regex\": \"Opera%20Touch/(\\\\d+[\\\\.\\\\d]+)? CFNetwork/.+Darwin/.+(?!.*x86_64)\",\n \"name\": \"Opera Touch\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"WebKit\"\n }\n },\n {\n \"regex\": \"OPT/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"Opera Touch\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Blink\"\n }\n },\n {\n \"regex\": \"Opera/(\\\\d+[\\\\.\\\\d]+).+Opera Mobi\",\n \"name\": \"Opera Mobile\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Presto\",\n \"versions\": {\n \"15\": \"Blink\"\n }\n }\n },\n {\n \"regex\": \"Opera ?Mini/(?:att/)?(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"Opera Mini\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Presto\"\n }\n },\n {\n \"regex\": \"Opera ?Mini.+Version/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"Opera Mini\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Presto\"\n }\n },\n {\n \"regex\": \"OPiOS/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"Opera Mini iOS\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"WebKit\"\n }\n },\n {\n \"regex\": \"Opera%20Mini/(\\\\d+[\\\\.\\\\d]+) CFNetwork\",\n \"name\": \"Opera Mini iOS\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"WebKit\"\n }\n },\n {\n \"regex\": \"Opera.+Edition Next.+Version/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"Opera Next\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Presto\",\n \"versions\": {\n \"15\": \"Blink\"\n }\n }\n },\n {\n \"regex\": \"(?:Opera|OPR)[/ ](?:9.80.*Version/)?(\\\\d+[\\\\.\\\\d]+).+Edition Next\",\n \"name\": \"Opera Next\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Presto\",\n \"versions\": {\n \"15\": \"Blink\"\n }\n }\n },\n {\n \"regex\": \"(?:Opera[/ ]?|OPR[/ ])(?:9.80.*Version/)?(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"Opera\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"\",\n \"versions\": {\n \"7\": \"Presto\",\n \"15\": \"Blink\",\n \"3.5\": \"Elektra\"\n }\n }\n },\n {\n \"regex\": \"rekonq(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Rekonq\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"WebKit\"\n }\n },\n {\n \"regex\": \"CoolNovo(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"CoolNovo\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"\"\n }\n },\n {\n \"regex\": \"(?:Comodo[ _])?Dragon(?!fruit)(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Comodo Dragon\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"WebKit\",\n \"versions\": {\n \"28\": \"Blink\"\n }\n }\n },\n {\n \"regex\": \"ChromePlus(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"ChromePlus\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"\"\n }\n },\n {\n \"regex\": \"Conkeror(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Conkeror\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Gecko\"\n }\n },\n {\n \"regex\": \"Konqueror(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Konqueror\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"KHTML\",\n \"versions\": {\n \"4\": \"\"\n }\n }\n },\n {\n \"regex\": \"(?:baidubrowser|bdbrowser(?:(?:hd)?_i18n)?|FlyFlow|BaiduHD)(?:[/ ](\\\\d+[\\\\.\\\\d]*))?\",\n \"name\": \"Baidu Browser\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"(?:(?:BD)?Spark(?:Safe)?|BIDUBrowser)[/ ](\\\\d+[\\\\.\\\\d]*)\",\n \"name\": \"Baidu Spark\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"YaBrowser(?:/(\\\\d+[\\\\.\\\\d]*)) \\\\(lite\\\\)?\",\n \"name\": \"Yandex Browser Lite\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Blink\"\n }\n },\n {\n \"regex\": \"YaBrowser(?:/(\\\\d+[\\\\.\\\\d]*))(?: \\\\((alpha|beta)\\\\))?\",\n \"name\": \"Yandex Browser\",\n \"version\": \"$1 $2\",\n \"engine\": {\n \"default\": \"Blink\"\n }\n },\n {\n \"regex\": \"Ya(?:ndex)?SearchBrowser(?:/(\\\\d+[\\\\.\\\\d]*))\",\n \"name\": \"Yandex Browser\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Blink\"\n }\n },\n {\n \"regex\": \"Vivaldi(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Vivaldi\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Blink\"\n }\n },\n {\n \"regex\": \"TweakStyle(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"TweakStyle\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Blink\"\n }\n },\n {\n \"regex\": \"Chrome.+Midori Browser/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"Midori\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Blink\"\n }\n },\n {\n \"regex\": \"Midori(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Midori\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"WebKit\"\n }\n },\n {\n \"regex\": \"Mercury(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Mercury\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Chrome.+Maxthon(?:.+\\\\(portable\\\\))?/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"Maxthon\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"WebKit\",\n \"versions\": {\n \"5.2\": \"Blink\"\n }\n }\n },\n {\n \"regex\": \"(?:Maxthon(?:%20Browser)?|MxBrowser(?:-inhouse|-iPhone)?)[ /](\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"Maxthon\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"\",\n \"versions\": {\n \"3\": \"WebKit\"\n }\n }\n },\n {\n \"regex\": \"(?:Maxthon|MyIE2)\",\n \"name\": \"Maxthon\",\n \"version\": \"\",\n \"engine\": {\n \"default\": \"\"\n }\n },\n {\n \"regex\": \"Puffin(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Puffin\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"MobileIron(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Iron Mobile\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Blink\"\n }\n },\n {\n \"regex\": \"Chrome(?:/(\\\\d+[\\\\.\\\\d]+))?.*Iron\",\n \"name\": \"Iron\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"WebKit\"\n }\n },\n {\n \"regex\": \"Iron(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Iron\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"WebKit\",\n \"versions\": {\n \"28\": \"Blink\"\n }\n }\n },\n {\n \"regex\": \"Epiphany(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"GNOME Web\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Gecko\",\n \"versions\": {\n \"2.9.16\": \"\",\n \"2.28\": \"WebKit\"\n }\n }\n },\n {\n \"regex\": \"LieBaoFast(?:[ /](\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"LieBaoFast\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"LBBrowser(?:[ /](\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Cheetah Browser\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"SE (\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"Sogou Explorer\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"QQBrowserLite/([\\\\d\\\\.]+)\",\n \"name\": \"QQ Browser Lite\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"M?QQBrowser/Mini([\\\\.\\\\d]+)?\",\n \"name\": \"QQ Browser Mini\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"\"\n }\n },\n {\n \"regex\": \"M?QQBrowser(?:/([\\\\.\\\\d]+))?\",\n \"name\": \"QQ Browser\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"\"\n }\n },\n {\n \"regex\": \"(?:MIUIBrowser|MiuiBrowser)(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"MIUI Browser\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"\"\n }\n },\n {\n \"regex\": \"(?:coc_coc_browser|coccocbrowser|CocCoc)(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Coc Coc\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"WebKit\",\n \"versions\": {\n \"28\": \"Blink\"\n }\n }\n },\n {\n \"regex\": \"DuckDuckGo/(\\\\d+[\\\\.\\\\d]*)\",\n \"name\": \"DuckDuckGo Privacy Browser\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Blink\"\n }\n },\n {\n \"regex\": \"Samsung ?Browser(?:[/ ](\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Samsung Browser\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"(?:SFB(?:rowser)?)/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"Super Fast Browser\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"com.browser.tssomas(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Super Fast Browser\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"EUI Browser(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"EUI Browser\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"UBrowser(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"UBrowser\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Streamy(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Streamy\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"WebKit\"\n }\n },\n {\n \"regex\": \"isivioo\",\n \"name\": \"Isivioo\",\n \"version\": \"\",\n \"engine\": {\n \"default\": \"WebKit\"\n }\n },\n {\n \"regex\": \"UC[ ]?Browser(?:[ /]?(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"UC Browser\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"UCWEB(?:[ /]?(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"UC Browser\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"UC AppleWebKit\",\n \"name\": \"UC Browser\",\n \"version\": \"\"\n },\n {\n \"regex\": \"Tenta/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"Tenta Browser\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"WebKit\"\n }\n },\n {\n \"regex\": \"Rocket/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"Firefox Rocket\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"WebKit\"\n }\n },\n {\n \"regex\": \"Web Explorer/(\\\\d+[\\\\.\\\\d]+).*Chrome\",\n \"name\": \"Web Explorer\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"WebKit\"\n }\n },\n {\n \"regex\": \"SznProhlizec/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"Seznam Browser\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Blink\"\n }\n },\n {\n \"regex\": \"SogouMobileBrowser/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"Sogou Mobile Browser\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"\"\n }\n },\n {\n \"regex\": \"Mint Browser/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"Mint Browser\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"\"\n }\n },\n {\n \"regex\": \"Ecosia (?:android|ios)@(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"Ecosia\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"\"\n }\n },\n {\n \"regex\": \"ACHEETAHI\",\n \"name\": \"CM Browser\",\n \"version\": \"\",\n \"engine\": {\n \"default\": \"\"\n }\n },\n {\n \"regex\": \"Kiwi Chrome\",\n \"name\": \"Kiwi\",\n \"version\": \"\",\n \"engine\": {\n \"default\": \"\"\n }\n },\n {\n \"regex\": \"Mb2345Browser/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"2345 Browser\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"\"\n }\n },\n {\n \"regex\": \"QtWebEngine/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"QtWebEngine\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"\"\n }\n },\n {\n \"regex\": \"Silk/(\\\\d+[\\\\.\\\\d]+) like Chrome\",\n \"name\": \"Mobile Silk\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Blink\"\n }\n },\n {\n \"regex\": \"Silk(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Mobile Silk\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"WebKit\"\n }\n },\n {\n \"regex\": \"IBrowse(?:[ /](\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"IBrowse\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"UP.Browser(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Openwave Mobile Browser\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Openwave(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Openwave Mobile Browser\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"OneBrowser(?:[ /](\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"ONE Browser\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"WebKit\"\n }\n },\n {\n \"regex\": \"(?:NokiaBrowser|BrowserNG)(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Nokia Browser\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Series60/5\\\\.0\",\n \"name\": \"Nokia Browser\",\n \"version\": \"7.0\"\n },\n {\n \"regex\": \"Series60/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"Nokia OSS Browser\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"S40OviBrowser/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"Nokia Ovi Browser\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"^Nokia|Nokia[EN]?\\\\d+\",\n \"name\": \"Nokia Browser\",\n \"version\": \"\"\n },\n {\n \"regex\": \"Sleipnir(?:(?:%20Browser)?[ /](\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Sleipnir\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"\"\n }\n },\n {\n \"regex\": \"NTENTBrowser(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"NTENT Browser\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"TV Bro/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"TV Bro\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Chrome/.+ Quark(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Quark\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"WebKit\"\n }\n },\n {\n \"regex\": \"MonumentBrowser(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Monument Browser\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"WebKit\"\n }\n },\n {\n \"regex\": \"BlueBrowser(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Blue Browser\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"WebKit\"\n }\n },\n {\n \"regex\": \"JAPAN Browser(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Japan Browser\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"WebKit\"\n }\n },\n {\n \"regex\": \"Version/.* Chrome(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Chrome Webview\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"WebKit\",\n \"versions\": {\n \"28\": \"Blink\"\n }\n }\n },\n {\n \"regex\": \"CrMo(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Chrome Mobile\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"WebKit\",\n \"versions\": {\n \"28\": \"Blink\"\n }\n }\n },\n {\n \"regex\": \"CriOS(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Chrome Mobile iOS\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"WebKit\"\n }\n },\n {\n \"regex\": \"Chrome(?:/(\\\\d+[\\\\.\\\\d]+))? Mobile\",\n \"name\": \"Chrome Mobile\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"WebKit\",\n \"versions\": {\n \"28\": \"Blink\"\n }\n }\n },\n {\n \"regex\": \"chromeframe(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Chrome Frame\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"WebKit\"\n }\n },\n {\n \"regex\": \"Chromium(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Chromium\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"WebKit\",\n \"versions\": {\n \"28\": \"Blink\"\n }\n }\n },\n {\n \"regex\": \"HeadlessChrome(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Headless Chrome\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Blink\"\n }\n },\n {\n \"regex\": \"Chrome(?!book)(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Chrome\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"WebKit\",\n \"versions\": {\n \"28\": \"Blink\"\n }\n }\n },\n {\n \"regex\": \"(?:Tizen|SLP) Browser(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Tizen Browser\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Blazer(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Palm Blazer\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Pre/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"Palm Pre\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"(?:hpw|web)OS/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"wOSBrowser\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"WebPro(?:[ /](\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Palm WebPro\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Palmscape(?:[ /](\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Palmscape\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Jasmine(?:[ /](\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Jasmine\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Lynx(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Lynx\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Text-based\"\n }\n },\n {\n \"regex\": \"NCSA_Mosaic(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"NCSA Mosaic\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"ABrowse(?: (\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"ABrowse\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"amaya(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Amaya\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"AmigaVoyager(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Amiga Voyager\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Amiga-Aweb(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Amiga Aweb\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Arora(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Arora\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"WebKit\"\n }\n },\n {\n \"regex\": \"Beonex(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Beonex\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Gecko\"\n }\n },\n {\n \"regex\": \"bline(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"B-Line\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"WebKit\"\n }\n },\n {\n \"regex\": \"BrowseX \\\\((\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"BrowseX\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Charon(?:[/ ](\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Charon\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Cheshire(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Cheshire\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"dbrowser\",\n \"name\": \"dbrowser\",\n \"version\": \"\",\n \"engine\": {\n \"default\": \"WebKit\"\n }\n },\n {\n \"regex\": \"Dillo(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Dillo\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Dillo\"\n }\n },\n {\n \"regex\": \"Dolfin(?:/(\\\\d+[\\\\.\\\\d]+))?|dolphin\",\n \"name\": \"Dolphin\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"WebKit\"\n }\n },\n {\n \"regex\": \"Elinks(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Elinks\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Text-based\"\n }\n },\n {\n \"regex\": \"Element Browser(?:[ /](\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Element Browser\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"eZBrowser(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"eZ Browser\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Firebird(?! Build)(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Firebird\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Gecko\"\n }\n },\n {\n \"regex\": \"Fluid(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Fluid\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"WebKit\"\n }\n },\n {\n \"regex\": \"Galeon(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Galeon\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Gecko\"\n }\n },\n {\n \"regex\": \"Google Earth(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Google Earth\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"WebKit\"\n }\n },\n {\n \"regex\": \"HotJava(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"HotJava\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"iCabMobile(?:[ /](\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"iCab Mobile\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"WebKit\"\n }\n },\n {\n \"regex\": \"iCab(?:[ /](\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"iCab\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"iCab\",\n \"versions\": {\n \"4\": \"WebKit\"\n }\n }\n },\n {\n \"regex\": \"i?Lunascape(?:[/ ](\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Lunascape\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"\"\n }\n },\n {\n \"regex\": \"Crazy Browser (\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"Crazy Browser\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Trident\"\n }\n },\n {\n \"regex\": \"IEMobile[ /](\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"IE Mobile\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Trident\"\n }\n },\n {\n \"regex\": \"MSIE (\\\\d+[\\\\.\\\\d]+).*XBLWP7\",\n \"name\": \"IE Mobile\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Trident\"\n }\n },\n {\n \"regex\": \"MSIE.*Trident/4.0\",\n \"name\": \"Internet Explorer\",\n \"version\": \"8.0\",\n \"engine\": {\n \"default\": \"Trident\"\n }\n },\n {\n \"regex\": \"MSIE.*Trident/5.0\",\n \"name\": \"Internet Explorer\",\n \"version\": \"9.0\",\n \"engine\": {\n \"default\": \"Trident\"\n }\n },\n {\n \"regex\": \"MSIE.*Trident/6.0\",\n \"name\": \"Internet Explorer\",\n \"version\": \"10.0\",\n \"engine\": {\n \"default\": \"Trident\"\n }\n },\n {\n \"regex\": \"Trident/[78].0\",\n \"name\": \"Internet Explorer\",\n \"version\": \"11.0\",\n \"engine\": {\n \"default\": \"Trident\"\n }\n },\n {\n \"regex\": \"MSIE (\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"Internet Explorer\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Trident\"\n }\n },\n {\n \"regex\": \"IE[ /](\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"Internet Explorer\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Trident\"\n }\n },\n {\n \"regex\": \"Kindle/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"Kindle Browser\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"K-meleon(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"K-meleon\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Gecko\"\n }\n },\n {\n \"regex\": \"Links(?: \\\\((\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Links\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Text-based\"\n }\n },\n {\n \"regex\": \"LG Browser(?:/(\\\\d+[\\\\.\\\\d]+))\",\n \"name\": \"LG Browser\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"LuaKit(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"LuaKit\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"OmniWeb(?:/[v]?(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"OmniWeb\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"WebKit\"\n }\n },\n {\n \"regex\": \"Phoenix(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Phoenix\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"NetFrontLifeBrowser(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"NetFront Life\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"NetFront\"\n }\n },\n {\n \"regex\": \"NetFront(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"NetFront\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"NetFront\"\n }\n },\n {\n \"regex\": \"PLAYSTATION|NINTENDO 3|AppleWebKit.+ N[XF]/\\\\d+\\\\.\\\\d+\\\\.\\\\d+\",\n \"name\": \"NetFront\",\n \"version\": \"\"\n },\n {\n \"regex\": \"NetPositive(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"NetPositive\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Odyssey Web Browser(?:.*OWB/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Odyssey Web Browser\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"OffByOne\",\n \"name\": \"Off By One\",\n \"version\": \"\"\n },\n {\n \"regex\": \"(?:Oregano|OreganMediaBrowser)(?:[ /](\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Oregano\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"(?:Polaris|Embider)(?:[/ ](\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Polaris\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"SEMC-Browser(?:[/ ](\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"SEMC-Browser\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Shiira(?:[/ ](\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Shiira\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"WebKit\"\n }\n },\n {\n \"regex\": \"Skyfire(?:[/ ](\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Skyfire\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Snowshoe(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Snowshoe\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"WebKit\"\n }\n },\n {\n \"regex\": \"Sunrise(?:Browser)?(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Sunrise\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"SuperBird(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"SuperBird\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Vision-Browser(?:/(\\\\d+[\\\\.\\\\d]+))\",\n \"name\": \"Vision Mobile Browser\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"WeTab-Browser\",\n \"name\": \"WeTab Browser\",\n \"version\": \"\"\n },\n {\n \"regex\": \"Xiino(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Xiino\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"BlackBerry|PlayBook|BB10\",\n \"name\": \"BlackBerry Browser\",\n \"version\": \"\"\n },\n {\n \"regex\": \"Android\",\n \"name\": \"Android Browser\",\n \"version\": \"\",\n \"engine\": {\n \"default\": \"WebKit\"\n }\n },\n {\n \"regex\": \"Coast(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Coast\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Surf(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"surf\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"WebKit\"\n }\n },\n {\n \"regex\": \"(?:(?:iPod|iPad|iPhone).+Version|MobileSafari)/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"Mobile Safari\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"WebKit\"\n }\n },\n {\n \"regex\": \"(?:Version/(\\\\d+[\\\\.\\\\d]+).*)?Mobile.*Safari/\",\n \"name\": \"Mobile Safari\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"WebKit\"\n }\n },\n {\n \"regex\": \"(?:iPod|iPhone|iPad)\",\n \"name\": \"Mobile Safari\",\n \"version\": \"\",\n \"engine\": {\n \"default\": \"WebKit\"\n }\n },\n {\n \"regex\": \"Version/(\\\\d+[\\\\.\\\\d]+).*Safari/|Safari/?\\\\d+\",\n \"name\": \"Safari\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"WebKit\"\n }\n },\n {\n \"regex\": \"Dorado WAP-Browser[/ ](\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"Dorado\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"NetSurf(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"NetSurf\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"NetSurf\"\n }\n },\n {\n \"regex\": \"Uzbl\",\n \"name\": \"Uzbl\",\n \"version\": \"\"\n },\n {\n \"regex\": \"SimpleBrowser\",\n \"name\": \"SimpleBrowser\",\n \"version\": \"\"\n },\n {\n \"regex\": \"Zvu(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Zvu\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"Gecko\"\n }\n },\n {\n \"regex\": \"GOGGalaxyClient/(\\\\d+[\\\\.\\\\d]+)?\",\n \"name\": \"GOG Galaxy\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"WAP Browser/MAUI|(?:\\\\w+)?Maui Wap Browser|MAUI[- ]Browser\",\n \"name\": \"MAUI WAP Browser\",\n \"version\": \"\"\n },\n {\n \"regex\": \"SP%20Browser/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"SP Browser\",\n \"version\": \"$1\",\n \"engine\": {\n \"default\": \"WebKit\"\n }\n }\n]\n", "[\n {\n \"regex\": \"NetFront\",\n \"name\": \"NetFront\"\n },\n {\n \"regex\": \"Edge\",\n \"name\": \"Edge\"\n },\n {\n \"regex\": \"Trident\",\n \"name\": \"Trident\"\n },\n {\n \"regex\": \"(? {\n const result = {\n type: \"\",\n name: \"\",\n version: \"\",\n engine: \"\",\n engineVersion: \"\"\n };\n for (const browser of browsers_json_1.default) {\n const match = user_agent_1.userAgentParser(browser.regex, userAgent);\n if (!match)\n continue;\n const vrpVersion = variable_replacement_1.variableReplacement(browser.version, match);\n const version = version_1.formatVersion(vrpVersion, this.options.versionTruncation);\n const shortVersion = version && parseFloat(version_1.formatVersion(vrpVersion, 1)) || \"\";\n if (browser.engine) {\n result.engine = browser.engine.default;\n if (browser.engine && browser.engine.versions && shortVersion) {\n const sortedEngineVersions = Object.entries(browser.engine.versions).sort((a, b) => {\n return parseFloat(a[0]) > parseFloat(b[0]) ? 1 : -1;\n });\n for (const [versionThreshold, engineByVersion] of sortedEngineVersions) {\n if (parseFloat(versionThreshold) <= shortVersion) {\n result.engine = engineByVersion || \"\";\n }\n }\n }\n }\n result.type = \"browser\";\n result.name = variable_replacement_1.variableReplacement(browser.name, match);\n result.version = version;\n break;\n }\n if (!result.engine) {\n for (const browserEngine of browser_engine_json_1.default) {\n let match = null;\n try {\n match = RegExp(browserEngine.regex, \"i\").exec(userAgent);\n }\n catch (_a) {\n // TODO: find out why it fails in some browsers\n }\n if (!match)\n continue;\n result.engine = browserEngine.name;\n break;\n }\n }\n result.engineVersion = version_1.formatVersion(version_1.parseBrowserEngineVersion(userAgent, result.engine), this.options.versionTruncation);\n return result;\n };\n this.options = Object.assign(Object.assign({}, this.options), options);\n }\n}\nexports.default = BrowserParser;\nBrowserParser.getBrowserShortName = (browserName) => {\n for (const [shortName, name] of Object.entries(available_browsers_json_1.default)) {\n if (name === browserName) {\n return shortName;\n }\n }\n return \"\";\n};\nBrowserParser.isMobileOnlyBrowser = (browserName) => {\n return mobile_only_browsers_json_1.default.includes(BrowserParser.getBrowserShortName(browserName));\n};\n", "[\n {\n \"regex\": \"AndroidDownloadManager(?:[ /]([\\\\d\\\\.]+))?\",\n \"name\": \"AndroidDownloadManager\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"(?:Apple)?News(?:[ /][\\\\d\\\\.]+)? Version(?:[ /]([\\\\d\\\\.]+))?\",\n \"name\": \"Apple News\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"bPod\",\n \"name\": \"bPod\",\n \"version\": \"\"\n },\n {\n \"regex\": \"MessengerLiteForiOS.(?:FBAV)(?:[ /]([\\\\d\\\\.]+))?\",\n \"name\": \"Facebook Messenger Lite\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"(?:MessengerForiOS|MESSENGER).(?:FBAV)(?:[ /]([\\\\d\\\\.]+))?\",\n \"name\": \"Facebook Messenger\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"(?:FBAV|com.facebook.katana)(?:[ /]([\\\\d\\\\.]+))?\",\n \"name\": \"Facebook\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"(?:FBAN|FBSV|FBID|FBBV)/\",\n \"name\": \"Facebook\",\n \"version\": \"\"\n },\n {\n \"regex\": \"FeedR(?:/([\\\\d\\\\.]+))?\",\n \"name\": \"FeedR\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"com.google.android.apps.searchlite\",\n \"name\": \"Google Go\",\n \"version\": \"\"\n },\n {\n \"regex\": \"com.google.android.apps.magazines\",\n \"name\": \"Google Play Newsstand\",\n \"version\": \"\"\n },\n {\n \"regex\": \"com.google.GooglePlus\",\n \"name\": \"Google Plus\",\n \"version\": \"\"\n },\n {\n \"regex\": \"MicroMessenger/([^; ]+)\",\n \"name\": \"WeChat\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"WeChatShareExtensionNew/([\\\\d\\\\.]+)\",\n \"name\": \"WeChat Share Extension\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"DingTalk/([0-9\\\\.]+)\",\n \"name\": \"DingTalk\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \".*__weibo__([0-9\\\\.]+)__\",\n \"name\": \"Sina Weibo\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Pinterest(?: for (?:Android|iOS))?(?:/([\\\\d\\\\.]+))?\",\n \"name\": \"Pinterest\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Podcatcher Deluxe\",\n \"name\": \"Podcatcher Deluxe\",\n \"version\": \"\"\n },\n {\n \"regex\": \"com.google.android.youtube(?:/([\\\\d\\\\.]+))?\",\n \"name\": \"YouTube\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"([^/]+)/(\\\\d+(?:\\\\.\\\\d+)+) \\\\((?:iPhone|iPad); iOS [0-9\\\\.]+; Scale/[0-9\\\\.]+\\\\)\",\n \"name\": \"$1\",\n \"version\": \"$2\"\n },\n {\n \"regex\": \"WhatsApp(?:[ /]([\\\\d\\\\.]+))?\",\n \"name\": \"WhatsApp\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Line(?:[ /]([\\\\d\\\\.]+))\",\n \"name\": \"Line\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Instacast(?:HD)?/(\\\\d\\\\.[\\\\d\\\\.abc]+) CFNetwork/([\\\\d\\\\.]+) Darwin/([\\\\d\\\\.]+)\",\n \"name\": \"Instacast\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Podcasts/([\\\\d\\\\.]+)\",\n \"name\": \"Podcasts\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Pocket Casts(?:, (?:Android|iOS) v([\\\\d\\\\.]+))?\",\n \"name\": \"Pocket Casts\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Podcat/([\\\\d\\\\.]+)\",\n \"name\": \"Podcat\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"BeyondPod\",\n \"name\": \"BeyondPod\",\n \"version\": null\n },\n {\n \"regex\": \"AntennaPod/?([\\\\d\\\\.]+)?\",\n \"name\": \"AntennaPod\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Overcast/([\\\\d\\\\.]+)\",\n \"name\": \"Overcast\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"(?:CastBox|fm.castbox.audiobook.radio.podcast)/?([\\\\d\\\\.]+)?\",\n \"name\": \"CastBox\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Player FM\",\n \"name\": \"Player FM\",\n \"version\": \"\"\n },\n {\n \"regex\": \"Podkicker(?: Pro)?/([\\\\d\\\\.]+)\",\n \"name\": \"Podkicker\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"PodcastRepublic/([\\\\d\\\\.]+)\",\n \"name\": \"Podcast Republic\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Castro/(\\\\d+)\",\n \"name\": \"Castro\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Castro 2 ([\\\\d\\\\.]+)/[\\\\d]+ Like iTunes\",\n \"name\": \"Castro 2\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Castro 2\",\n \"name\": \"Castro 2\",\n \"version\": \"\"\n },\n {\n \"regex\": \"DoggCatcher\",\n \"name\": \"DoggCatcher\",\n \"version\": null\n },\n {\n \"regex\": \"PodcastAddict/v([\\\\d]+)\",\n \"name\": \"Podcast & Radio Addict\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Podcat(?:%202)?/([\\\\d]+) CFNetwork\",\n \"name\": \"Podcat\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"iCatcher[^\\\\d]+([\\\\d\\\\.]+)\",\n \"name\": \"iCatcher\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"YelpApp/([\\\\d\\\\.]+)\",\n \"name\": \"Yelp Mobile\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"jp.co.yahoo.android.yjtop/([\\\\d\\\\.]+)\",\n \"name\": \"Yahoo! Japan\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"RSSRadio/([\\\\d]+)?\",\n \"name\": \"RSSRadio\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"SogouSearch Android[\\\\d\\\\.]+ version([\\\\d\\\\.]+)?\",\n \"name\": \"SogouSearch App\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"NewsArticle/([\\\\d\\\\.]+)?\",\n \"name\": \"NewsArticle App\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"tieba/([\\\\d\\\\.]+)?\",\n \"name\": \"tieba\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"com\\\\.douban\\\\.group/([\\\\d\\\\.]+)?\",\n \"name\": \"douban App\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"BingWeb/([\\\\d\\\\.]+)?\",\n \"name\": \"BingWebApp\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"(?:com.google.GoogleMobile|GSA)/([\\\\d\\\\.]+)?\",\n \"name\": \"Google Search App\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Flipboard/([\\\\d\\\\.]+)?\",\n \"name\": \"Flipboard App\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Instagram[ /]([\\\\d\\\\.]+)?\",\n \"name\": \"Instagram App\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"baiduboxapp/([\\\\d\\\\.]+)?\",\n \"name\": \"Baidu Box App\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Crosswalk(?!.*(?:Streamy|QwantMobile))/([\\\\d\\\\.]+)?\",\n \"name\": \"CrosswalkApp\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Twitter for iPhone[/]?([\\\\d\\\\.]+)?\",\n \"name\": \"Twitter\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Twitter/([\\\\d\\\\.]+)\",\n \"name\": \"Twitter\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"TwitterAndroid[/]?([\\\\d\\\\.]+)?\",\n \"name\": \"Twitter\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"TopBuzz/([\\\\d\\\\.]+)\",\n \"name\": \"TopBuzz\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Snapchat/([\\\\d\\\\.]+)\",\n \"name\": \"Snapchat\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"UnityPlayer/([\\\\d\\\\.]+)\",\n \"name\": \"UnityPlayer\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"UCURSOS/v([\\\\d\\\\.]+)\",\n \"name\": \"U-Cursos\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"HeyTapBrowser/([\\\\d\\\\.]+)\",\n \"name\": \"HeyTapBrowser\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"RobloxApp/([\\\\d\\\\.]+)\",\n \"name\": \"Roblox\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Viber/([\\\\d\\\\.]+)\",\n \"name\": \"Viber\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Siri/1\",\n \"name\": \"Siri\",\n \"version\": \"1.0\"\n },\n {\n \"regex\": \"LinkedIn/([\\\\d\\\\.]+)\",\n \"name\": \"LinkedIn\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Instapaper/([\\\\d\\\\.]+)\",\n \"name\": \"Instapaper\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Keeper/([\\\\d\\\\.]+)\",\n \"name\": \"Keeper Password Manager\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Skyeng Teachers/([\\\\d\\\\.]+)\",\n \"name\": \"Skyeng Teachers\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Kik/([\\\\d\\\\.]+) \\\\(Android\",\n \"name\": \"Kik\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Procast/?([\\\\d\\\\.]+)?\",\n \"name\": \"Procast\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"DeviantArt/([\\\\d\\\\.]+)\",\n \"name\": \"DeviantArt\",\n \"version\": \"\"\n },\n {\n \"regex\": \"Discord/([\\\\d\\\\.]+)\",\n \"name\": \"Discord\",\n \"version\": \"\"\n },\n {\n \"regex\": \"Covenant%20Eyes/([\\\\d\\\\.]+)\",\n \"name\": \"Covenant Eyes\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"HP%20Smart/([\\\\d\\\\.]+)\",\n \"name\": \"HP Smart\",\n \"version\": \"\"\n },\n {\n \"regex\": \"Bitsboard/([\\\\d\\\\.]+)\",\n \"name\": \"Bitsboard\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Betbull/([\\\\d\\\\.]+)\",\n \"name\": \"BetBull\",\n \"version\": \"\"\n },\n {\n \"regex\": \"U-Cursos/([\\\\d\\\\.]+)\",\n \"name\": \"U-Cursos\",\n \"version\": \"\"\n },\n {\n \"regex\": \"1PasswordThumbs/([\\\\d\\\\.]+)\",\n \"name\": \"1Password\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"(?:Microsoft Office )?(Access|Excel|OneDrive for Business|OneNote|PowerPoint|Project|Publisher|Visio|Word)[ /]([\\\\d\\\\.]+)\",\n \"name\": \"Microsoft Office $1\",\n \"version\": \"$2\"\n },\n {\n \"regex\": \"OneDriveiOSApp/([\\\\d\\\\.]+)\",\n \"name\": \"Microsoft OneDrive\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Microsoft Office Mobile/([\\\\d\\\\.]+)\",\n \"name\": \"Microsoft Office Mobile\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"OC/([\\\\d\\\\.]+) \\\\(Skype for Business\\\\)\",\n \"name\": \"Skype for Business\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"TikTok[/ ](\\\\d+\\\\.(?:[\\\\.\\\\d]+))?\",\n \"name\": \"TikTok\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"NAVER/(\\\\d+[\\\\.\\\\d]+) CFNetwork\",\n \"name\": \"Naver\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Copied/(\\\\d+[\\\\.\\\\d]+) CFNetwork\",\n \"name\": \"Copied\",\n \"version\": \"\"\n },\n {\n \"regex\": \"Pic%20Collage/(\\\\d+[\\\\.\\\\d]+) CFNetwork\",\n \"name\": \"Pic Collage\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Papers/(\\\\d+[\\\\.\\\\d]+) CFNetwork\",\n \"name\": \"Papers\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"RoboForm/(\\\\d+[\\\\.\\\\d]+) CFNetwork\",\n \"name\": \"RoboForm\",\n \"version\": \"\"\n },\n {\n \"regex\": \"Slack/(\\\\d+[\\\\.\\\\d]+) CFNetwork\",\n \"name\": \"Slack\",\n \"version\": \"\"\n },\n {\n \"regex\": \"KAKAOTALK (\\\\d+\\\\.(?:[\\\\.\\\\d]+))?\",\n \"name\": \"KakaoTalk\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"ShopeeVN/([\\\\d\\\\.]+)\",\n \"name\": \"Shopee\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"SPORT1/([\\\\d\\\\.]+)\",\n \"name\": \"SPORT1\",\n \"version\": \"\"\n },\n {\n \"regex\": \"Clovia/([\\\\d\\\\.]+)\",\n \"name\": \"Clovia\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"ShowMe/([\\\\d\\\\.]+)\",\n \"name\": \"ShowMe\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Wattpad/([\\\\d\\\\.]+)\",\n \"name\": \"Wattpad\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"WSJ/([\\\\d\\\\.]+)\",\n \"name\": \"The Wall Street Journal\",\n \"version\": \"\"\n },\n {\n \"regex\": \"WH%20Questions/([\\\\d\\\\.]+)\",\n \"name\": \"WH Questions\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"whisper/([\\\\d\\\\.]+)\",\n \"name\": \"Whisper\",\n \"version\": \"\"\n },\n {\n \"regex\": \"Opal/([\\\\d\\\\.]+)\",\n \"name\": \"Opal Travel\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Zalo/([\\\\d\\\\.]+)\",\n \"name\": \"Zalo\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Yandex/([\\\\d\\\\.]+)\",\n \"name\": \"Yandex\",\n \"version\": \"\"\n },\n {\n \"regex\": \"Zoho%20Chat/([\\\\d\\\\.]+)\",\n \"name\": \"Zoho Chat\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Thunder/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"Thunder\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"CGNBrowser/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"CGN\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Podbean/.+App (\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"Podbean\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"AlexaMediaPlayer/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"Alexa Media Player\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"TuneIn Radio Pro(?:[^/]+)?/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"TuneIn Radio Pro\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"TuneIn Radio(?:[^/]+)?/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"TuneIn Radio\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Podcaster/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"Podcaster\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Swoot/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"Swoot\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"RadioPublic/android-(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"RadioPublic\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Podimo/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"Podimo\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"com.evolve.podcast/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"Evolve Podcast\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \" Rocket.Chat\\\\+?/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"Rocket Chat\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"^Pandora Audio.+Android\",\n \"name\": \"Pandora\",\n \"version\": \"\"\n },\n {\n \"regex\": \"^NPROneAndroid\",\n \"name\": \"NPR One\",\n \"version\": \"\"\n },\n {\n \"regex\": \"^WirtschaftsWoche-iOS-(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"Wirtschafts Woche\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"^Outlook-iOS/.+\\\\((\\\\d+[\\\\.\\\\d]+)\\\\)$\",\n \"name\": \"Microsoft Outlook\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"^TVirl/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"TVirl\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"2?chMate/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"ChMate\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"2tch/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"2tch\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Ciisaa/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"Ciisaa\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"BB2C (\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"BB2C\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"twinkle/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"twinkle\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"JaneStyle_iOS/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"JaneStyle\",\n \"version\": \"$1\"\n }\n]\n", "\"use strict\";\nvar __importDefault = (this && this.__importDefault) || function (mod) {\n return (mod && mod.__esModule) ? mod : { \"default\": mod };\n};\nObject.defineProperty(exports, \"__esModule\", { value: true });\nconst mobile_apps_json_1 = __importDefault(require(\"../../fixtures/regexes/client/mobile_apps.json\"));\nconst version_1 = require(\"../../utils/version\");\nconst variable_replacement_1 = require(\"../../utils/variable-replacement\");\nconst user_agent_1 = require(\"../../utils/user-agent\");\nclass MobileAppParser {\n constructor(options) {\n this.options = {\n versionTruncation: 1\n };\n this.parse = (userAgent) => {\n const result = {\n type: \"\",\n name: \"\",\n version: \"\"\n };\n for (const mobileApp of mobile_apps_json_1.default) {\n const match = user_agent_1.userAgentParser(mobileApp.regex, userAgent);\n if (!match)\n continue;\n result.type = \"mobile app\";\n result.name = variable_replacement_1.variableReplacement(mobileApp.name, match);\n result.version = version_1.formatVersion(variable_replacement_1.variableReplacement(mobileApp.version, match), this.options.versionTruncation);\n break;\n }\n return result;\n };\n this.options = Object.assign(Object.assign({}, this.options), options);\n }\n}\nexports.default = MobileAppParser;\n", "[\n {\n \"regex\": \"Akregator(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Akregator\",\n \"version\": \"$1\",\n \"url\": \"http://userbase.kde.org/Akregator\",\n \"type\": \"Feed Reader\"\n },\n {\n \"regex\": \"Apple-PubSub(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Apple PubSub\",\n \"version\": \"$1\",\n \"url\": \"https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man1/pubsub.1.html\",\n \"type\": \"Feed Reader\"\n },\n {\n \"regex\": \"BashPodder\",\n \"name\": \"BashPodder\",\n \"version\": \"\",\n \"url\": \"http://lincgeek.org/bashpodder/\",\n \"type\": \"Feed Reader\"\n },\n {\n \"regex\": \"Breaker/v([\\\\d\\\\.]+)\",\n \"name\": \"Breaker\",\n \"version\": \"$1\",\n \"url\": \"https://www.breaker.audio/\",\n \"type\": \"Feed Reader App\"\n },\n {\n \"regex\": \"Downcast/([\\\\d\\\\.]+)\",\n \"name\": \"Downcast\",\n \"version\": \"$1\",\n \"url\": \"http://downcastapp.com/\",\n \"type\": \"Feed Reader App\"\n },\n {\n \"regex\": \"FeedDemon(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"FeedDemon\",\n \"version\": \"$1\",\n \"url\": \"http://www.feeddemon.com/\",\n \"type\": \"Feed Reader\"\n },\n {\n \"regex\": \"Feeddler(?:RSS|PRO)(?:[/ ](\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Feeddler RSS Reader\",\n \"version\": \"$1\",\n \"url\": \"http://www.chebinliu.com/projects/iphone/feeddler-rss-reader/\",\n \"type\": \"Feed Reader App\"\n },\n {\n \"regex\": \"QuiteRSS(?:[/ ](\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"QuiteRSS\",\n \"version\": \"$1\",\n \"url\": \"https://quiterss.org\",\n \"type\": \"Feed Reader App\"\n },\n {\n \"regex\": \"gPodder/([\\\\d\\\\.]+)\",\n \"name\": \"gPodder\",\n \"version\": \"$1\",\n \"url\": \"http://gpodder.org/\",\n \"type\": \"Feed Reader App\"\n },\n {\n \"regex\": \"JetBrains Omea Reader(?:[/ ](\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"JetBrains Omea Reader\",\n \"version\": \"$1\",\n \"url\": \"http://www.jetbrains.com/omea/reader/\",\n \"type\": \"Feed Reader\"\n },\n {\n \"regex\": \"Liferea(?:[/ ](\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Liferea\",\n \"version\": \"$1\",\n \"url\": \"http://liferea.sf.net/\",\n \"type\": \"Feed Reader\"\n },\n {\n \"regex\": \"(?:NetNewsWire|Evergreen.+MacOS)(?:[/ ](\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"NetNewsWire\",\n \"version\": \"$1\",\n \"url\": \"http://netnewswireapp.com/\",\n \"type\": \"Feed Reader\"\n },\n {\n \"regex\": \"NewsBlur (?:iPhone|iPad) App(?: v(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"NewsBlur Mobile App\",\n \"version\": \"$1\",\n \"url\": \"http://www.newsblur.com\",\n \"type\": \"Feed Reader App\"\n },\n {\n \"regex\": \"NewsBlur(?:/(\\\\d+[\\\\.\\\\d]+))\",\n \"name\": \"NewsBlur\",\n \"version\": \"$1\",\n \"url\": \"http://www.newsblur.com\",\n \"type\": \"Feed Reader\"\n },\n {\n \"regex\": \"newsbeuter(?:[/ ](\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Newsbeuter\",\n \"version\": \"$1\",\n \"url\": \"http://www.newsbeuter.org/\",\n \"type\": \"Feed Reader\"\n },\n {\n \"regex\": \"PritTorrent/([\\\\d\\\\.]+)\",\n \"name\": \"PritTorrent\",\n \"version\": \"$1\",\n \"url\": \"http://bitlove.org\",\n \"type\": \"Feed Reader\"\n },\n {\n \"regex\": \"Pulp[/ ](\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"Pulp\",\n \"version\": \"$1\",\n \"url\": \"http://www.acrylicapps.com/pulp/\",\n \"type\": \"Feed Reader App\"\n },\n {\n \"regex\": \"ReadKit(?:[/ ](\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"ReadKit\",\n \"version\": \"$1\",\n \"url\": \"http://readkitapp.com/\",\n \"type\": \"Feed Reader App\"\n },\n {\n \"regex\": \"Reeder[/ ](\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"Reeder\",\n \"version\": \"$1\",\n \"url\": \"http://reederapp.com/\",\n \"type\": \"Feed Reader App\"\n },\n {\n \"regex\": \"RSSBandit(?:[/ ](\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"RSS Bandit\",\n \"version\": \"$1\",\n \"url\": \"http://www.rssbandit.org)\",\n \"type\": \"Feed Reader\"\n },\n {\n \"regex\": \"RSS Junkie(?:[/ ](\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"RSS Junkie\",\n \"version\": \"$1\",\n \"url\": \"https://play.google.com/store/apps/details?id=com.bitpowder.rssjunkie\",\n \"type\": \"Feed Reader App\"\n },\n {\n \"regex\": \"RSSOwl(?:[/ ](\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"RSSOwl\",\n \"version\": \"$1\",\n \"url\": \"http://www.rssowl.org/\",\n \"type\": \"Feed Reader\"\n },\n {\n \"regex\": \"Stringer\",\n \"name\": \"Stringer\",\n \"version\": \"\",\n \"url\": \"https://github.com/swanson/stringer\",\n \"type\": \"Feed Reader\"\n }\n]\n", "\"use strict\";\nvar __importDefault = (this && this.__importDefault) || function (mod) {\n return (mod && mod.__esModule) ? mod : { \"default\": mod };\n};\nObject.defineProperty(exports, \"__esModule\", { value: true });\nconst feed_readers_json_1 = __importDefault(require(\"../../fixtures/regexes/client/feed_readers.json\"));\nconst version_1 = require(\"../../utils/version\");\nconst variable_replacement_1 = require(\"../../utils/variable-replacement\");\nconst user_agent_1 = require(\"../../utils/user-agent\");\nclass FeedReaderParser {\n constructor(options) {\n this.options = {\n versionTruncation: 1\n };\n this.parse = (userAgent) => {\n const result = {\n type: \"\",\n name: \"\",\n version: \"\",\n url: \"\"\n };\n for (const feedReader of feed_readers_json_1.default) {\n const match = user_agent_1.userAgentParser(feedReader.regex, userAgent);\n if (!match)\n continue;\n result.type = \"feed reader\";\n result.name = variable_replacement_1.variableReplacement(feedReader.name, match);\n result.version = version_1.formatVersion(variable_replacement_1.variableReplacement(feedReader.version, match), this.options.versionTruncation);\n result.url = feedReader.url;\n break;\n }\n return result;\n };\n this.options = Object.assign(Object.assign({}, this.options), options);\n }\n}\nexports.default = FeedReaderParser;\n", "[\n {\n \"regex\": \"Wget(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Wget\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Guzzle(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Guzzle (PHP HTTP Client)\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"(?:lib)?curl(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"curl\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"python-requests(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Python Requests\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Python-urllib(?:/?(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Python urllib\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Java(?:/?(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Java\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"(?:perlclient|libwww-perl)(?:/?(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Perl\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"okhttp/([\\\\d\\\\.]+)\",\n \"name\": \"OkHttp\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"HTTP_Request2(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"HTTP_Request2\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"HTTP_Request2(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"HTTP_Request2\",\n \"version\": \"$1\",\n \"url\": \"http://pear.php.net/package/http_request2\"\n },\n {\n \"regex\": \"Mechanize(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Mechanize\",\n \"version\": \"$1\",\n \"url\": \"http://github.com/sparklemotion/mechanize/\"\n },\n {\n \"regex\": \"aiohttp(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"aiohttp\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Google-HTTP-Java-Client(?:/(\\\\d+[\\\\.\\\\d\\\\w-]+))?\",\n \"name\": \"Google HTTP Java Client\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"WWW-Mechanize(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"WWW-Mechanize\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Faraday(?: v(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Faraday\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"(?:Go-http-client|Go )/?(?:(\\\\d+[\\\\.\\\\d]+))?(?: package http)?\",\n \"name\": \"Go-http-client\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"urlgrabber(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"urlgrabber (yum)\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"libdnf(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"libdnf\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"HTTPie(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"HTTPie\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"rest-client/(\\\\d+[\\\\.\\\\d]+).*ruby\",\n \"name\": \"REST Client for Ruby\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"RestSharp/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"RestSharp\",\n \"version\": \"$1\",\n \"url\": \"http://restsharp.org/\"\n },\n {\n \"regex\": \"scalaj-http/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"ScalaJ HTTP\",\n \"version\": \"$1\",\n \"url\": \"https://github.com/scalaj/scalaj-http\"\n },\n {\n \"regex\": \"REST::Client/(\\\\d+)\",\n \"name\": \"Perl REST::Client\",\n \"version\": \"$1\",\n \"url\": \"https://metacpan.org/pod/REST::Client\"\n },\n {\n \"regex\": \"node-fetch/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"Node Fetch\",\n \"version\": \"$1\",\n \"url\": \"https://github.com/node-fetch/node-fetch\"\n },\n {\n \"regex\": \"ReactorNetty/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"ReactorNetty\",\n \"version\": \"$1\",\n \"url\": \"https://github.com/reactor/reactor-netty\"\n },\n {\n \"regex\": \"PostmanRuntime(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Postman Desktop\",\n \"version\": \"$1\",\n \"url\": \"https://github.com/postmanlabs/postman-runtime\"\n },\n {\n \"regex\": \"Jakarta Commons-HttpClient/([\\\\.\\\\d]+)\",\n \"name\": \"Jakarta Commons HttpClient\",\n \"version\": \"$1\",\n \"url\": \"https://hc.apache.org/httpclient-3.x\"\n },\n {\n \"regex\": \"WinHttp.WinHttpRequest.+([\\\\.\\\\d]+)\",\n \"name\": \"WinHttp WinHttpRequest\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Embarcadero URI Client/([\\\\.\\\\d]+)\",\n \"name\": \"Embarcadero URI Client\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Mikrotik/([\\\\.\\\\d]+)\",\n \"name\": \"Mikrotik Fetch\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"GRequests(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"GRequests\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"akka-http/([\\\\.\\\\d]+)\",\n \"name\": \"Akka HTTP\",\n \"version\": \"$1\"\n }\n]\n", "\"use strict\";\nvar __importDefault = (this && this.__importDefault) || function (mod) {\n return (mod && mod.__esModule) ? mod : { \"default\": mod };\n};\nObject.defineProperty(exports, \"__esModule\", { value: true });\nconst libraries_json_1 = __importDefault(require(\"../../fixtures/regexes/client/libraries.json\"));\nconst version_1 = require(\"../../utils/version\");\nconst variable_replacement_1 = require(\"../../utils/variable-replacement\");\nconst user_agent_1 = require(\"../../utils/user-agent\");\nclass LibraryParser {\n constructor(options) {\n this.options = {\n versionTruncation: 1\n };\n this.parse = (userAgent) => {\n const result = {\n type: \"\",\n name: \"\",\n version: \"\",\n url: \"\"\n };\n for (const library of libraries_json_1.default) {\n const match = user_agent_1.userAgentParser(library.regex, userAgent);\n if (!match)\n continue;\n result.type = \"library\";\n result.name = variable_replacement_1.variableReplacement(library.name, match);\n result.version = version_1.formatVersion(variable_replacement_1.variableReplacement(library.version, match), this.options.versionTruncation);\n result.url = library.url || \"\";\n break;\n }\n return result;\n };\n this.options = Object.assign(Object.assign({}, this.options), options);\n }\n}\nexports.default = LibraryParser;\n", "[\n {\n \"regex\": \"Audacious(?:[ /]([\\\\d\\\\.]+))?\",\n \"name\": \"Audacious\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Banshee(?:[ /]([\\\\d\\\\.]+))?\",\n \"name\": \"Banshee\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Boxee(?:[ /]([\\\\d\\\\.]+))?\",\n \"name\": \"Boxee\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Clementine(?:[ /]([\\\\d\\\\.]+))?\",\n \"name\": \"Clementine\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Deezer(?:/([\\\\d\\\\.]+))?\",\n \"name\": \"Deezer\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"iTunes(?:-iPhone|-iPad)?(?:/([\\\\d\\\\.]+))?\",\n \"name\": \"iTunes\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"FlyCast(?:/([\\\\d\\\\.]+))?\",\n \"name\": \"FlyCast\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"foobar2000(?:/([\\\\d\\\\.]+))?\",\n \"name\": \"Foobar2000\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"MediaMonkey(?:[ /](\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"MediaMonkey\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Miro(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Miro\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"NexPlayer(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"NexPlayer\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Nightingale(?:/([\\\\d\\\\.]+))?\",\n \"name\": \"Nightingale\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"QuickTime(?:(?:(?:.+qtver=)|(?:(?: E-)?[\\\\./]))([\\\\d\\\\.]+))?\",\n \"name\": \"QuickTime\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Songbird(?:/([\\\\d\\\\.]+))?\",\n \"name\": \"Songbird\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"SubStream(?:/([\\\\d\\\\.]+))?\",\n \"name\": \"SubStream\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Sonos/([\\\\d\\\\.]+)?\",\n \"name\": \"SONOS\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"(?:Lib)?VLC(?:/([\\\\d\\\\.]+))?\",\n \"name\": \"VLC\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Winamp(?:MPEG)?(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Winamp\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"(?:Windows-Media-Player|NSPlayer)(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Windows Media Player\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"XBMC(?:/([\\\\d\\\\.]+))?\",\n \"name\": \"XBMC\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Kodi(?:/([\\\\d\\\\.]+))?\",\n \"name\": \"Kodi\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"stagefright(?:/([\\\\d\\\\.]+))?\",\n \"name\": \"Stagefright\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"GoogleChirp(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Google Podcasts\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Music Player Daemon (?:(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Music Player Daemon\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"mpv (?:(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"mpv\",\n \"version\": \"$1\"\n }\n]\n", "\"use strict\";\nvar __importDefault = (this && this.__importDefault) || function (mod) {\n return (mod && mod.__esModule) ? mod : { \"default\": mod };\n};\nObject.defineProperty(exports, \"__esModule\", { value: true });\nconst mediaplayers_json_1 = __importDefault(require(\"../../fixtures/regexes/client/mediaplayers.json\"));\nconst version_1 = require(\"../../utils/version\");\nconst variable_replacement_1 = require(\"../../utils/variable-replacement\");\nconst user_agent_1 = require(\"../../utils/user-agent\");\nclass MediaPlayerParser {\n constructor(options) {\n this.options = {\n versionTruncation: 1\n };\n this.parse = (userAgent) => {\n const result = {\n type: \"\",\n name: \"\",\n version: \"\"\n };\n for (const mediaPlayer of mediaplayers_json_1.default) {\n const match = user_agent_1.userAgentParser(mediaPlayer.regex, userAgent);\n if (!match)\n continue;\n result.type = \"media player\";\n result.name = variable_replacement_1.variableReplacement(mediaPlayer.name, match);\n result.version = version_1.formatVersion(variable_replacement_1.variableReplacement(mediaPlayer.version, match), this.options.versionTruncation);\n break;\n }\n return result;\n };\n this.options = Object.assign(Object.assign({}, this.options), options);\n }\n}\nexports.default = MediaPlayerParser;\n", "[\n {\n \"regex\": \"Outlook-Express(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Outlook Express\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Microsoft Outlook(?:[/ ](\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Microsoft Outlook\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"(?:Thunderbird|Icedove|Shredder)(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Thunderbird\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Airmail(?: (\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Airmail\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Lotus-Notes(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Lotus Notes\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Barca(?:Pro)?(?:[/ ](\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Barca\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Postbox(?:[/ ](\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Postbox\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"MailBar(?:[/ ](\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"MailBar\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"The Bat!(?: Voyager)?(?:[/ ](\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"The Bat!\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"DAVdroid(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"DAVdroid\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"(?:SeaMonkey|Iceape)(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"SeaMonkey\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Live5ch/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"Live5ch\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"JaneView/\",\n \"name\": \"JaneView\",\n \"version\": \"\"\n },\n {\n \"regex\": \"BathyScaphe/\",\n \"name\": \"BathyScaphe\",\n \"version\": \"\"\n }\n]\n", "\"use strict\";\nvar __importDefault = (this && this.__importDefault) || function (mod) {\n return (mod && mod.__esModule) ? mod : { \"default\": mod };\n};\nObject.defineProperty(exports, \"__esModule\", { value: true });\nconst pim_json_1 = __importDefault(require(\"../../fixtures/regexes/client/pim.json\"));\nconst version_1 = require(\"../../utils/version\");\nconst variable_replacement_1 = require(\"../../utils/variable-replacement\");\nconst user_agent_1 = require(\"../../utils/user-agent\");\nclass PersonalInformationManagerParser {\n constructor(options) {\n this.options = {\n versionTruncation: 1\n };\n this.parse = (userAgent) => {\n const result = {\n type: \"\",\n name: \"\",\n version: \"\"\n };\n for (const personalInformationManager of pim_json_1.default) {\n const match = user_agent_1.userAgentParser(personalInformationManager.regex, userAgent);\n if (!match)\n continue;\n result.type = \"personal information manager\";\n result.name = variable_replacement_1.variableReplacement(personalInformationManager.name, match);\n result.version = version_1.formatVersion(variable_replacement_1.variableReplacement(personalInformationManager.version, match), this.options.versionTruncation);\n break;\n }\n return result;\n };\n this.options = Object.assign(Object.assign({}, this.options), options);\n }\n}\nexports.default = PersonalInformationManagerParser;\n", "\"use strict\";\nvar __importDefault = (this && this.__importDefault) || function (mod) {\n return (mod && mod.__esModule) ? mod : { \"default\": mod };\n};\nObject.defineProperty(exports, \"__esModule\", { value: true });\nconst browser_1 = __importDefault(require(\"./browser\"));\nconst mobile_apps_1 = __importDefault(require(\"./mobile-apps\"));\nconst feed_readers_1 = __importDefault(require(\"./feed-readers\"));\nconst libraries_1 = __importDefault(require(\"./libraries\"));\nconst media_players_1 = __importDefault(require(\"./media-players\"));\nconst personal_information_managers_1 = __importDefault(require(\"./personal-information-managers\"));\nconst clientParsers = [\n feed_readers_1.default,\n mobile_apps_1.default,\n media_players_1.default,\n personal_information_managers_1.default,\n browser_1.default,\n libraries_1.default\n];\nclass ClientParser {\n constructor(options) {\n this.options = {\n versionTruncation: 1\n };\n this.parse = (userAgent) => {\n for (const Parser of clientParsers) {\n const parser = new Parser(this.options);\n const client = parser.parse(userAgent);\n if (client.type !== \"\")\n return client;\n }\n return null;\n };\n this.options = Object.assign(Object.assign({}, this.options), options);\n }\n}\nexports.default = ClientParser;\n", "{\n \"Nikon\": {\n \"regex\": \"Coolpix S800c\",\n \"device\": \"camera\",\n \"model\": \"Coolpix S800c\"\n },\n \"Samsung\": {\n \"regex\": \"EK-G[CN][0-9]{3}\",\n \"device\": \"camera\",\n \"models\": [\n {\n \"regex\": \"EK-GN120\",\n \"model\": \"Galaxy NX\"\n },\n {\n \"regex\": \"EK-GC100\",\n \"model\": \"Galaxy Camera\"\n },\n {\n \"regex\": \"EK-GC110\",\n \"model\": \"Galaxy Camera WiFi only\"\n },\n {\n \"regex\": \"EK-GC200\",\n \"model\": \"Galaxy Camera 2\"\n },\n {\n \"regex\": \"EK-GC([0-9]{3})\",\n \"model\": \"Galaxy Camera $1\"\n }\n ]\n }\n}\n", "\"use strict\";\nvar __importDefault = (this && this.__importDefault) || function (mod) {\n return (mod && mod.__esModule) ? mod : { \"default\": mod };\n};\nObject.defineProperty(exports, \"__esModule\", { value: true });\nconst cameras_json_1 = __importDefault(require(\"../../fixtures/regexes/device/cameras.json\"));\nconst variable_replacement_1 = require(\"../../utils/variable-replacement\");\nconst user_agent_1 = require(\"../../utils/user-agent\");\nclass CameraParser {\n constructor() {\n this.parse = (userAgent) => {\n const result = {\n type: \"\",\n brand: \"\",\n model: \"\"\n };\n for (const [brand, camera] of Object.entries(cameras_json_1.default)) {\n const match = user_agent_1.userAgentParser(camera.regex, userAgent);\n if (!match)\n continue;\n result.type = \"camera\";\n result.brand = brand;\n if (\"model\" in camera && camera.model) {\n result.model = variable_replacement_1.variableReplacement(camera.model, match).trim();\n }\n else if (\"models\" in camera && camera.models) {\n for (const model of camera.models) {\n const modelMatch = user_agent_1.userAgentParser(model.regex, userAgent);\n if (!modelMatch)\n continue;\n result.model = variable_replacement_1.variableReplacement(model.model, modelMatch).trim();\n break;\n }\n }\n break;\n }\n return result;\n };\n }\n}\nexports.default = CameraParser;\n", "{\n \"360\": {\n \"regex\": \"(?:180[1379]-A01|1713-A01|1707-A01|1509-A00|1603-A03|1607-A01|1503-A01)(?:[);/ ]|$)\",\n \"device\": \"smartphone\",\n \"models\": [\n {\n \"regex\": \"1807-A01(?:[);/ ]|$)\",\n \"model\": \"N7\"\n },\n {\n \"regex\": \"1803-A01(?:[);/ ]|$)\",\n \"model\": \"N7 Lite\"\n },\n {\n \"regex\": \"1809-A01(?:[);/ ]|$)\",\n \"model\": \"N7 Pro\"\n },\n {\n \"regex\": \"1713-A01(?:[);/ ]|$)\",\n \"model\": \"N6 Lite\"\n },\n {\n \"regex\": \"1707-A01(?:[);/ ]|$)\",\n \"model\": \"N6\"\n },\n {\n \"regex\": \"1801-A01(?:[);/ ]|$)\",\n \"model\": \"N6 Pro\"\n },\n {\n \"regex\": \"1509-A00(?:[);/ ]|$)\",\n \"model\": \"Q5 Plus\"\n },\n {\n \"regex\": \"1503-A01(?:[);/ ]|$)\",\n \"model\": \"N4\"\n },\n {\n \"regex\": \"1603-A03(?:[);/ ]|$)\",\n \"model\": \"N4A\"\n },\n {\n \"regex\": \"1607-A01(?:[);/ ]|$)\",\n \"model\": \"N5S\"\n }\n ]\n },\n \"8848\": {\n \"regex\": \"8848 (M[3-6])(?:[);/ ]|$)\",\n \"device\": \"smartphone\",\n \"model\": \"$1\"\n },\n \"Ace\": {\n \"regex\": \"(?:BUZZ [12]|BUZZ 2|CLEVER 1|URBAN 1(?: Pro)?)(?:[);/ ]|$)\",\n \"device\": \"smartphone\",\n \"models\": [\n {\n \"regex\": \"BUZZ ([12]) Lite\",\n \"model\": \"Buzz $1 Lite\"\n },\n {\n \"regex\": \"BUZZ ([12]) Plus\",\n \"model\": \"Buzz $1 Plus\"\n },\n {\n \"regex\": \"BUZZ ([12])\",\n \"model\": \"Buzz $1\"\n },\n {\n \"regex\": \"CLEVER 1\",\n \"model\": \"Clever 1\"\n },\n {\n \"regex\": \"URBAN 1 Pro\",\n \"model\": \"Urban 1 Pro\"\n },\n {\n \"regex\": \"URBAN 1\",\n \"model\": \"Urban 1\"\n }\n ]\n },\n \"Tunisie Telecom\": {\n \"regex\": \"StarTrail TT(?:[);/ ]|$)\",\n \"device\": \"smartphone\",\n \"model\": \"StarTrail by TT\"\n },\n \"SFR\": {\n \"regex\": \"StarShine|StarTrail|STARADDICT|StarText|StarNaute|StarXtrem|StarTab\",\n \"device\": \"smartphone\",\n \"models\": [\n {\n \"regex\": \"StarXtrem II(?:[);/ ]|$)\",\n \"model\": \"StarXtrem 2\"\n },\n {\n \"regex\": \"StarXtrem ?([3-6])(?:[);/ ]|$)\",\n \"model\": \"StarXtrem $1\"\n },\n {\n \"regex\": \"StarXtrem(?:[);/ ]|$)\",\n \"model\": \"StarXtrem\"\n },\n {\n \"regex\": \"StarTrail III(?:[);/ ]|$)\",\n \"model\": \"StarTrail 3\"\n },\n {\n \"regex\": \"StarTrail II(?:[);/ ]|$)\",\n \"model\": \"StarTrail 2\"\n },\n {\n \"regex\": \"StarTrail[ _]?([1-9])[ _]([34])G(?:[);/ ]|$)\",\n \"model\": \"StarTrail $1 $2G\"\n },\n {\n \"regex\": \"StarTrail[ _]?([1-9])(?:[);/ ]|$)\",\n \"model\": \"StarTrail $1\"\n },\n {\n \"regex\": \"StarTrail(?:[);/ ]|$)\",\n \"model\": \"StarTrail\"\n },\n {\n \"regex\": \"StarShine II(?:[);/ ]|$)\",\n \"model\": \"StarShine 2\"\n },\n {\n \"regex\": \"StarShine(?:[);/ ]|$)\",\n \"model\": \"StarShine\"\n },\n {\n \"regex\": \"STARADDICT 4(?:[);/ ]|$)\",\n \"model\": \"Staraddict 4\"\n },\n {\n \"regex\": \"STARADDICT III(?:[);/ ]|$)\",\n \"model\": \"Staraddict 3\"\n },\n {\n \"regex\": \"STARADDICT II Plus(?:[);/ ]|$)\",\n \"model\": \"Staraddict 2 Plus\"\n },\n {\n \"regex\": \"STARADDICT II(?:[);/ ]|$)\",\n \"model\": \"Staraddict 2\"\n },\n {\n \"regex\": \"STARADDICT(?:[);/ ]|$)\",\n \"model\": \"Staraddict\"\n },\n {\n \"regex\": \"StarText II(?:[);/ ]|$)\",\n \"model\": \"StarText 2\"\n },\n {\n \"regex\": \"StarText(?:[);/ ]|$)\",\n \"model\": \"StarText\"\n },\n {\n \"regex\": \"StarNaute II(?:[);/ ]|$)\",\n \"model\": \"StarNaute 2\"\n },\n {\n \"regex\": \"StarNaute(?:[);/ ]|$)\",\n \"model\": \"StarNaute\"\n },\n {\n \"regex\": \"StarTab\",\n \"model\": \"StarTab\",\n \"device\": \"tablet\"\n },\n {\n \"regex\": \"((?:StarShine|StarTrail|STARADDICT|StarText|StarNaute|StarXtrem)[^;/]*) Build\",\n \"model\": \"$1\"\n }\n ]\n },\n \"HTC\": {\n \"regex\": \"HTC|Sprint (?:APA|ATP)|ADR(?!910L)[a-z0-9]+|NexusHD2|Amaze[ _]4G(?:[);/ ]|$)|(Desire|Sensation|Evo ?3D|IncredibleS|Wildfire|Butterfly)[ _]?([^;/)]+)(?: Build|\\\\))|(Amaze[ _]4G|(? {\n model = model.replace(/_/g, \" \");\n model = model.replace(RegExp(\" TD$\", \"i\"), \"\");\n if (model === \"Build\")\n return \"\";\n return model;\n};\n", "\"use strict\";\nvar __importDefault = (this && this.__importDefault) || function (mod) {\n return (mod && mod.__esModule) ? mod : { \"default\": mod };\n};\nObject.defineProperty(exports, \"__esModule\", { value: true });\nconst mobiles_json_1 = __importDefault(require(\"../../fixtures/regexes/device/mobiles.json\"));\nconst variable_replacement_1 = require(\"../../utils/variable-replacement\");\nconst user_agent_1 = require(\"../../utils/user-agent\");\nconst model_1 = require(\"../../utils/model\");\nclass MobileParser {\n constructor() {\n this.parse = (userAgent) => {\n const result = {\n type: \"\",\n brand: \"\",\n model: \"\"\n };\n let resultType = \"\";\n for (const [brand, mobile] of Object.entries(mobiles_json_1.default)) {\n const match = user_agent_1.userAgentParser(mobile.regex, userAgent);\n if (!match)\n continue;\n resultType = \"device\" in mobile && mobile.device || \"\";\n result.brand = brand;\n if (\"model\" in mobile && mobile.model) {\n result.model = model_1.buildModel(variable_replacement_1.variableReplacement(mobile.model, match)).trim();\n }\n else if (\"models\" in mobile && mobile.models) {\n for (const model of mobile.models) {\n const modelMatch = user_agent_1.userAgentParser(model.regex, userAgent);\n if (!modelMatch)\n continue;\n result.model = model_1.buildModel(variable_replacement_1.variableReplacement(model.model, modelMatch)).trim();\n if (\"device\" in model && model.device) {\n resultType = model.device;\n }\n if (\"brand\" in model) {\n result.brand = model.brand || \"\";\n }\n break;\n }\n }\n break;\n }\n // Sanitize device type\n if (resultType === \"tv\") {\n result.type = \"television\";\n }\n else if (resultType === \"car browser\") {\n result.type = \"car\";\n }\n else {\n result.type = resultType;\n }\n // Sanitize device brand\n if (result.brand === \"Unknown\") {\n result.brand = \"\";\n }\n return result;\n };\n }\n}\nexports.default = MobileParser;\n", "{\n \"Airties\": {\n \"regex\": \"Airties\",\n \"device\": \"tv\",\n \"models\": [\n {\n \"regex\": \"Airties; ?([^);/]+)\",\n \"model\": \"$1\"\n }\n ]\n },\n \"ALDI NORD\": {\n \"regex\": \"ALDINORD[;,]\",\n \"device\": \"tv\",\n \"model\": \"\"\n },\n \"ALDI S\u00DCD\": {\n \"regex\": \"ALDISUED[;,]\",\n \"device\": \"tv\",\n \"model\": \"\"\n },\n \"Altech UEC\": {\n \"regex\": \"Altech UEC\",\n \"device\": \"tv\",\n \"models\": [\n {\n \"regex\": \"Altech UEC; ?([^);/]+)\",\n \"model\": \"$1\"\n }\n ]\n },\n \"AOC\": {\n \"regex\": \"AOC\",\n \"device\": \"tv\",\n \"models\": [\n {\n \"regex\": \"(LE43S5970-20|S50856)\",\n \"model\": \"$1\"\n }\n ]\n },\n \"ARRIS\": {\n \"regex\": \"ARRIS[;,]\",\n \"device\": \"tv\",\n \"models\": [\n {\n \"regex\": \"FS-ARS-01B\",\n \"model\": \"FS-ARS-01B\"\n }\n ]\n },\n \"Atvio\": {\n \"regex\": \"ATVIO\",\n \"device\": \"tv\",\n \"models\": [\n {\n \"regex\": \"55D1620\",\n \"model\": \"55D1620\"\n }\n ]\n },\n \"BangOlufsen\": {\n \"regex\": \"Bangolufsen\",\n \"device\": \"tv\",\n \"model\": \"BeoVision\"\n },\n \"Blaupunkt\": {\n \"regex\": \"Blaupunkt_UMC[;,]\",\n \"device\": \"tv\",\n \"models\": [\n {\n \"regex\": \"LC-65CUG8052E\",\n \"model\": \"LC-65CUG8052E\"\n }\n ]\n },\n \"Bush\": {\n \"regex\": \"BUSH[;,]\",\n \"device\": \"tv\",\n \"model\": \"\"\n },\n \"Celcus\": {\n \"regex\": \"CELCUS[;,]\",\n \"device\": \"tv\",\n \"model\": \"\"\n },\n \"Changhong\": {\n \"regex\": \"Changhong\",\n \"device\": \"tv\",\n \"models\": [\n {\n \"regex\": \"Changhong; ?([^);/]+)\",\n \"model\": \"$1\"\n }\n ]\n },\n \"CreNova\": {\n \"regex\": \"CreNova\",\n \"device\": \"tv\",\n \"model\": \"CNV001\"\n },\n \"Digihome\": {\n \"regex\": \"DIGIHOME[;,]\",\n \"device\": \"tv\",\n \"model\": \"\"\n },\n \"DMM\": {\n \"regex\": \"DMM\",\n \"device\": \"tv\",\n \"model\": \"Dreambox\"\n },\n \"ELECTRONIA\": {\n \"regex\": \"ELECTRONIA[;,]\",\n \"device\": \"tv\",\n \"model\": \"\"\n },\n \"Essentielb\": {\n \"regex\": \"ESSENTIELB[;,]\",\n \"device\": \"tv\",\n \"model\": \"\"\n },\n \"Finlux\": {\n \"regex\": \"FINLUX[;,]\",\n \"device\": \"tv\",\n \"model\": \"\"\n },\n \"F&U\": {\n \"regex\": \"FU[;,]\",\n \"device\": \"tv\",\n \"model\": \"\"\n },\n \"Fuego\": {\n \"regex\": \"FUEGO[;,]\",\n \"device\": \"tv\",\n \"model\": \"\"\n },\n \"GoGEN\": {\n \"regex\": \"GOGEN[;,]\",\n \"device\": \"tv\",\n \"model\": \"\"\n },\n \"Grundig\": {\n \"regex\": \"(OWB|Grundig|Arcelik)\",\n \"device\": \"tv\",\n \"model\": \"\"\n },\n \"Haier\": {\n \"regex\": \"(?:HHW_)?HAIER\",\n \"device\": \"tv\",\n \"models\": [\n {\n \"regex\": \"LE55X7000U\",\n \"model\": \"LE55X7000U\"\n }\n ]\n },\n \"Hi-Level\": {\n \"regex\": \"HI-LEVEL[;,]\",\n \"device\": \"tv\",\n \"model\": \"\"\n },\n \"Hisense\": {\n \"regex\": \"Hisense|Eurofins_Digital_Testing\",\n \"device\": \"tv\",\n \"models\": [\n {\n \"regex\": \"50ADEVTOOL\",\n \"model\": \"50ADEVTOOL\"\n },\n {\n \"regex\": \"50A683FEVS\",\n \"model\": \"50A683FEVS\"\n },\n {\n \"regex\": \"55A6100EE\",\n \"model\": \"55A6100EE\"\n },\n {\n \"regex\": \"55U62QGAVT\",\n \"model\": \"55U62QGAVT\"\n },\n {\n \"regex\": \"50A6502EA\",\n \"model\": \"50A6502EA\"\n },\n {\n \"regex\": \"MICALIDVB6886\",\n \"model\": \"MICALIDVB6886\"\n },\n {\n \"regex\": \"(L[A-Z]{2,3}[0-9]{2}[A-Z][0-9]{3,4}[A-Z]{0,6}[0-9]?[A-Z]?)\",\n \"model\": \"$1\"\n },\n {\n \"regex\": \"(H[A-Z]?[0-9]{2}[A-Z][0-9]{3,4}[A-Z]{0,4})\",\n \"model\": \"$1\"\n }\n ]\n },\n \"Hitachi\": {\n \"regex\": \"Hitachi[;,]\",\n \"device\": \"tv\",\n \"models\": [\n {\n \"regex\": \"49D2900\",\n \"model\": \"49D2900\"\n }\n ]\n },\n \"Horizon\": {\n \"regex\": \"HORIZON[;,]\",\n \"device\": \"tv\",\n \"model\": \"\"\n },\n \"Hotel\": {\n \"regex\": \"HOTEL[;,]\",\n \"device\": \"tv\",\n \"model\": \"\"\n },\n \"Humax\": {\n \"regex\": \"Humax\",\n \"device\": \"tv\",\n \"models\": [\n {\n \"regex\": \"(HD-FOX C|HD (FOX\\\\+|NANO)|iCord (HD\\\\+|MINI|Cable)|(CX|IR)HD-5100(C|S)|HM9503HD)\",\n \"model\": \"$1\"\n },\n {\n \"regex\": \"HMS1000S\",\n \"model\": \"HMS-1000S\"\n },\n {\n \"regex\": \"Humax; ([^);/]+)\",\n \"model\": \"$1\"\n }\n ]\n },\n \"Hyundai\": {\n \"regex\": \"HYUNDAI[;,]\",\n \"device\": \"tv\",\n \"model\": \"\"\n },\n \"Ikea\": {\n \"regex\": \"Ikea\",\n \"device\": \"tv\",\n \"model\": \"\"\n },\n \"Intek\": {\n \"regex\": \"Intek\",\n \"device\": \"tv\",\n \"models\": [\n {\n \"regex\": \"(Vantage|VT-100|VT-1)\",\n \"model\": \"$1\"\n }\n ]\n },\n \"Inverto\": {\n \"regex\": \"Inverto\",\n \"device\": \"tv\",\n \"models\": [\n {\n \"regex\": \"inverto; ([^);/]+)\",\n \"model\": \"$1\"\n },\n {\n \"regex\": \"(Volksbox Web Edition|Volksbox Essential|Volksbox II|Volksbox)\",\n \"model\": \"$1\"\n }\n ]\n },\n \"JVC\": {\n \"regex\": \"AFTSO001|JVC[;,]\",\n \"device\": \"tv\",\n \"models\": [\n {\n \"regex\": \"AFTSO001\",\n \"model\": \"4K (2019)\"\n }\n ]\n },\n \"Kalley\": {\n \"regex\": \"KALLEY[;,]\",\n \"device\": \"tv\",\n \"models\": [\n {\n \"regex\": \"32D1620\",\n \"model\": \"32D1620\"\n }\n ]\n },\n \"KUBO\": {\n \"regex\": \"KUBO[;,]\",\n \"device\": \"tv\",\n \"model\": \"\"\n },\n \"Laurus\": {\n \"regex\": \"LAURUS[;,]\",\n \"device\": \"tv\",\n \"model\": \"\"\n },\n \"LG\": {\n \"regex\": \"LGE\",\n \"device\": \"tv\",\n \"models\": [\n {\n \"regex\": \"XU43WT180N\",\n \"model\": \"XU43WT180N\"\n },\n {\n \"regex\": \"43LJ614V-ZA\",\n \"model\": \"43LJ614V-ZA\"\n },\n {\n \"regex\": \"55SK850V-ZA\",\n \"model\": \"55SK850V-ZA\"\n },\n {\n \"regex\": \"KEY0000213F1z\",\n \"model\": \"KEY0000213F1z\"\n },\n {\n \"regex\": \"KEY0000213F\",\n \"model\": \"KEY0000213F\"\n },\n {\n \"regex\": \"KEY000000(2E|2F|3B|3F)\",\n \"model\": \"KEY000000$1\"\n },\n {\n \"regex\": \"(NetCast [0-9]{1}.[0-9]{1}|GLOBAL_PLAT3)\",\n \"model\": \"$1\"\n },\n {\n \"regex\": \"(OLED[0-9]{2}[A-Z0-9]{3}[A-Z]{2})\",\n \"model\": \"$1\"\n },\n {\n \"regex\": \"(OLED[0-9]{2}[A-Z][0-9][A-Z])\",\n \"model\": \"$1\"\n },\n {\n \"regex\": \"(OLED[0-9]{2}[A-Z0-9]{2})\",\n \"model\": \"$1\"\n },\n {\n \"regex\": \"([0-9]{2}[A-Z]{2}[0-9]{4}[A-Z0-9]{1}[A-Z]{2})\",\n \"model\": \"$1\"\n },\n {\n \"regex\": \"([0-9]{2}NANO[0-9]{3}[A-Z]{2})\",\n \"model\": \"$1\"\n },\n {\n \"regex\": \"([0-9]{2}NANO[0-9]{2})\",\n \"model\": \"$1\"\n },\n {\n \"regex\": \"LGE;? ?([0-9]{2}[A-Z]{2}[0-9]{2,4}[A-Z]?)\",\n \"model\": \"$1\"\n },\n {\n \"regex\": \"LM21U\",\n \"model\": \"LM21U\"\n },\n {\n \"regex\": \"32LM\",\n \"model\": \"32LM\"\n }\n ]\n },\n \"Lifemaxx\": {\n \"regex\": \"Lifemaxx[;,]\",\n \"device\": \"tv\",\n \"model\": \"\"\n },\n \"Linsar\": {\n \"regex\": \"LINSAR[;,]\",\n \"device\": \"tv\",\n \"model\": \"\"\n },\n \"Loewe\": {\n \"regex\": \"Loewe\",\n \"device\": \"tv\",\n \"models\": [\n {\n \"regex\": \"([A-Z]{2}[0-9]{3})\",\n \"model\": \"$1\"\n }\n ]\n },\n \"Luxor\": {\n \"regex\": \"LUXOR[;,]\",\n \"device\": \"tv\",\n \"model\": \"\"\n },\n \"Manhattan\": {\n \"regex\": \"Manhattan\",\n \"device\": \"tv\",\n \"models\": [\n {\n \"regex\": \"T3\",\n \"model\": \"T3\"\n }\n ]\n },\n \"MediaTek\": {\n \"regex\": \"MTK|MediaTek;\",\n \"device\": \"tv\",\n \"models\": [\n {\n \"regex\": \"(MT[0-9]{4})\",\n \"model\": \"$1\"\n }\n ]\n },\n \"Medion\": {\n \"regex\": \"Medion\",\n \"device\": \"tv\",\n \"model\": \"\"\n },\n \"Miray\": {\n \"regex\": \"MIRAY\",\n \"device\": \"tv\",\n \"models\": [\n {\n \"regex\": \"LEDM-322NIP\",\n \"model\": \"LEDM-322NIP\"\n }\n ]\n },\n \"MStar\": {\n \"regex\": \"MStar[;,]\",\n \"device\": \"tv\",\n \"models\": [\n {\n \"regex\": \"([24])KTV18\",\n \"model\": \"$1KTV18\"\n }\n ]\n },\n \"MTC\": {\n \"regex\": \"MTC[;,]\",\n \"device\": \"tv\",\n \"models\": [\n {\n \"regex\": \"([24])K_Android_TV_V01\",\n \"model\": \"$1K Android TV\"\n }\n ]\n },\n \"Nordmende\": {\n \"regex\": \"NORDMENDE[;,]\",\n \"device\": \"tv\",\n \"model\": \"\"\n },\n \"Metz\": {\n \"regex\": \"Metz\",\n \"device\": \"tv\",\n \"model\": \"\"\n },\n \"Ok\": {\n \"regex\": \"OK[;,]\",\n \"device\": \"tv\",\n \"model\": \"\"\n },\n \"Panasonic\": {\n \"regex\": \"Panasonic\",\n \"device\": \"tv\",\n \"models\": [\n {\n \"regex\": \"VIERA (201[1-9])\",\n \"model\": \"VIERA ($1)\"\n },\n {\n \"regex\": \"(DIGA [A-Z]{1}[0-9]{4})\",\n \"model\": \"$1\"\n },\n {\n \"regex\": \"DIGA Webkit ([A-Z]{1}[0-9]{4})\",\n \"model\": \"DIGA $1\"\n },\n {\n \"regex\": \"SmartTV(201[89]|202[0-1])\",\n \"model\": \"Smart TV ($1)\"\n }\n ]\n },\n \"PEAQ\": {\n \"regex\": \"PEAQ\",\n \"device\": \"tv\",\n \"model\": \"\"\n },\n \"Philips\": {\n \"regex\": \"Philips|NETTV/\",\n \"device\": \"tv\",\n \"models\": [\n {\n \"regex\": \"Philips[,;] ?((?! )[^),;/]+)\",\n \"model\": \"$1\"\n },\n {\n \"regex\": \"NETTV/[0-9\\\\.]{5}\",\n \"model\": \"NetTV Series\"\n }\n ]\n },\n \"Polaroid\": {\n \"regex\": \"POLAROID[;,]\",\n \"device\": \"tv\",\n \"model\": \"\"\n },\n \"PROFiLO\": {\n \"regex\": \"PROFILO[;,]\",\n \"device\": \"tv\",\n \"model\": \"\"\n },\n \"Qilive\": {\n \"regex\": \"QILIVE[;,]\",\n \"device\": \"tv\",\n \"model\": \"\"\n },\n \"REGAL\": {\n \"regex\": \"REGAL[;,]\",\n \"device\": \"tv\",\n \"model\": \"\"\n },\n \"Saba\": {\n \"regex\": \"Saba[;,]\",\n \"device\": \"tv\",\n \"model\": \"\"\n },\n \"Salora\": {\n \"regex\": \"Salora[;,]\",\n \"device\": \"tv\",\n \"model\": \"\"\n },\n \"Samsung\": {\n \"regex\": \"Samsung|Maple_2011\",\n \"device\": \"tv\",\n \"models\": [\n {\n \"regex\": \"SmartTV(201[2-9]|202[0-1]):([^);/]+)\",\n \"model\": \"$2\"\n },\n {\n \"regex\": \"SmartTV(201[2-9]|202[0-1])\",\n \"model\": \"Smart TV ($1)\"\n },\n {\n \"regex\": \"Maple_2011\",\n \"model\": \"Smart TV (2011)\"\n }\n ]\n },\n \"SCBC\": {\n \"regex\": \"SCBC[;,]\",\n \"device\": \"tv\",\n \"models\": [\n {\n \"regex\": \"SCBC586\",\n \"model\": \"SCBC586\"\n },\n {\n \"regex\": \"43D1850\",\n \"model\": \"43D1850\"\n }\n ]\n },\n \"SEG\": {\n \"regex\": \"SEG[;,]\",\n \"device\": \"tv\",\n \"model\": \"\"\n },\n \"Selevision\": {\n \"regex\": \"Selevision\",\n \"device\": \"tv\",\n \"models\": [\n {\n \"regex\": \"Selevision; (?:Selevision )?([^);/]+)\",\n \"model\": \"$1\"\n },\n {\n \"regex\": \"(EMC1000i)\",\n \"model\": \"$1\"\n }\n ]\n },\n \"Sharp\": {\n \"regex\": \"(?:UMC-)?Sharp\",\n \"device\": \"tv\",\n \"models\": [\n {\n \"regex\": \"Sharp[,;] ?((?! |HbbTV)[^),;/]+)\",\n \"model\": \"$1\"\n },\n {\n \"regex\": \"(LE[0-9]{3}[A-Z]{0,3})\",\n \"model\": \"$1\"\n },\n {\n \"regex\": \"LC-([^);/]+)\",\n \"model\": \"LC-$1\"\n },\n {\n \"regex\": \"BLA-43\",\n \"model\": \"BLA-43\"\n }\n ]\n },\n \"Skyworth\": {\n \"regex\": \"Sky_worth\",\n \"device\": \"tv\",\n \"models\": [\n {\n \"regex\": \"Sky_worth;([^);/]+)\",\n \"model\": \"$1\"\n }\n ]\n },\n \"Smart\": {\n \"regex\": \"Smart[;,]\",\n \"device\": \"tv\",\n \"models\": [\n {\n \"regex\": \"Smart; ([^);/]+)\",\n \"model\": \"$1\"\n },\n {\n \"regex\": \"([A-Z]{2}[0-9]{2}|ZAPPIX)\",\n \"model\": \"$1\"\n }\n ]\n },\n \"Sony\": {\n \"regex\": \"Sony\",\n \"device\": \"tv\",\n \"models\": [\n {\n \"regex\": \"KDL-GR1\",\n \"model\": \"KDL-GR1\"\n },\n {\n \"regex\": \"KDL-GN([56])\",\n \"model\": \"KDL-GN$1\"\n },\n {\n \"regex\": \"BRAVIA (VH1|4K VH2)\",\n \"model\": \"BRAVIA $1\"\n },\n {\n \"regex\": \"(KDL?-?[0-9]{2}[A-Z]{1}[0-9]{4}[A-Z]{1})\",\n \"model\": \"$1\"\n },\n {\n \"regex\": \"(KDL?-?[0-9]{2}[A-Z]{1}[0-9]{3}[A-Z]{1})\",\n \"model\": \"$1\"\n },\n {\n \"regex\": \"(KDL?-?[0-9]{2}[A-Z]{1,2}[0-9]{1,5})\",\n \"model\": \"$1\"\n }\n ]\n },\n \"SWTV\": {\n \"regex\": \"SWTV[;,]\",\n \"device\": \"tv\",\n \"models\": [\n {\n \"regex\": \"SKWE20E21\",\n \"model\": \"SKWE20E21\"\n }\n ]\n },\n \"TD Systems\": {\n \"regex\": \"TDSystems[;,]\",\n \"device\": \"tv\",\n \"models\": [\n {\n \"regex\": \"SmartTV(2019|2020)\",\n \"model\": \"Smart TV ($1)\"\n }\n ]\n },\n \"Technicolor\": {\n \"regex\": \"Technicolor\",\n \"device\": \"tv\",\n \"models\": [\n {\n \"regex\": \"uzw4054ttg\",\n \"model\": \"UZW4054TTG\"\n }\n ]\n },\n \"Technika\": {\n \"regex\": \"TECHNIKA[;,]\",\n \"device\": \"tv\",\n \"model\": \"\"\n },\n \"TechniSat\": {\n \"regex\": \"TechniSat\",\n \"device\": \"tv\",\n \"models\": [\n {\n \"regex\": \"((DigiCorder|MultyVision|Digit) (ISIO S|ISIO C|ISIO))\",\n \"model\": \"$1\"\n }\n ]\n },\n \"TechnoTrend\": {\n \"regex\": \"TechnoTrend\",\n \"device\": \"tv\",\n \"models\": [\n {\n \"regex\": \"([A-Z]{1}-[0-9]{3})\",\n \"model\": \"$1\"\n }\n ]\n },\n \"Techwood\": {\n \"regex\": \"Techwood[;,]\",\n \"device\": \"tv\",\n \"model\": \"\"\n },\n \"Telefunken\": {\n \"regex\": \"Telefunken\",\n \"device\": \"tv\",\n \"model\": \"\"\n },\n \"TCL\": {\n \"regex\": \"TCL\",\n \"device\": \"tv\",\n \"models\": [\n {\n \"regex\": \"(32D1820|(?:39|55)D2900|32D2930|(?:32|43)S4900)\",\n \"model\": \"$1\"\n }\n ]\n },\n \"Thomson\": {\n \"regex\": \"THOMSON[,]?|THOM\",\n \"device\": \"tv\",\n \"models\": [\n {\n \"regex\": \"(TB28D19DHS-01|T28D18SFS-01B)\",\n \"model\": \"$1 28.0\\\"\"\n },\n {\n \"regex\": \"(T32RTM5040|T32D18SFS-01B)\",\n \"model\": \"$1 32.0\\\"\"\n },\n {\n \"regex\": \"(T43FSL5031|T43D18SFS-01B)\",\n \"model\": \"$1 43.0\\\"\"\n },\n {\n \"regex\": \"(T40D18SFS-01B)\",\n \"model\": \"$1 40.0\\\"\"\n },\n {\n \"regex\": \"(T49D18SFS-01B)\",\n \"model\": \"$1 49.0\\\"\"\n },\n {\n \"regex\": \"(T55D18[SD]FS-01B)\",\n \"model\": \"$1 55.0\\\"\"\n },\n {\n \"regex\": \"40FB5426\",\n \"model\": \"40FB5426\"\n }\n ]\n },\n \"TOKYO\": {\n \"regex\": \"TOKYO[;,]\",\n \"device\": \"tv\",\n \"models\": [\n {\n \"regex\": \"TOKTCLED32S\",\n \"model\": \"TOKTCLED32S\"\n }\n ]\n },\n \"Toshiba\": {\n \"regex\": \"Toshiba\",\n \"device\": \"tv\",\n \"models\": [\n {\n \"regex\": \"40L2600\",\n \"model\": \"40L2600\"\n },\n {\n \"regex\": \"(([0-9]{2}|DTV_)[A-Z]{2}[0-9]{1,3})\",\n \"model\": \"$1\"\n }\n ]\n },\n \"Videoweb\": {\n \"regex\": \"videoweb|tv2n\",\n \"device\": \"tv\",\n \"models\": [\n {\n \"regex\": \"(tv2n)\",\n \"model\": \"$1\"\n },\n {\n \"regex\": \"(videowebtv)\",\n \"model\": \"VideoWeb TV\"\n }\n ]\n },\n \"VOX\": {\n \"regex\": \"VOX[;,]\",\n \"device\": \"tv\",\n \"model\": \"\"\n },\n \"WELLINGTON\": {\n \"regex\": \"WELLINGTON[;,]\",\n \"device\": \"tv\",\n \"model\": \"\"\n },\n \"X.Vision\": {\n \"regex\": \"X-VISION[;,]\",\n \"device\": \"tv\",\n \"model\": \"\"\n },\n \"Vestel\": {\n \"regex\": \"(?:Vestel.+VESTEL|VESTEL;)\",\n \"device\": \"tv\",\n \"model\": \"\"\n }\n}\n", "\"use strict\";\nvar __importDefault = (this && this.__importDefault) || function (mod) {\n return (mod && mod.__esModule) ? mod : { \"default\": mod };\n};\nObject.defineProperty(exports, \"__esModule\", { value: true });\nconst televisions_json_1 = __importDefault(require(\"../../fixtures/regexes/device/televisions.json\"));\nconst variable_replacement_1 = require(\"../../utils/variable-replacement\");\nconst user_agent_1 = require(\"../../utils/user-agent\");\nconst model_1 = require(\"../../utils/model\");\nclass TelevisionParser {\n constructor() {\n this.parse = (userAgent) => {\n const result = {\n type: \"\",\n brand: \"\",\n model: \"\"\n };\n if (!this.isHbbTv(userAgent))\n return result;\n result.type = \"television\";\n for (const [brand, television] of Object.entries(televisions_json_1.default)) {\n const match = user_agent_1.userAgentParser(television.regex, userAgent);\n if (!match)\n continue;\n result.brand = brand;\n if (\"model\" in television && television.model) {\n result.model = model_1.buildModel(variable_replacement_1.variableReplacement(television.model, match)).trim();\n }\n else if (\"models\" in television && television.models) {\n for (const model of television.models) {\n const modelMatch = user_agent_1.userAgentParser(model.regex, userAgent);\n if (!modelMatch)\n continue;\n result.model = model_1.buildModel(variable_replacement_1.variableReplacement(model.model, modelMatch)).trim();\n break;\n }\n }\n break;\n }\n return result;\n };\n this.isHbbTv = (userAgent) => {\n return user_agent_1.userAgentParser(\"HbbTV\\/([1-9]{1}(?:\\.[0-9]{1}){1,2})\", userAgent);\n };\n }\n}\nexports.default = TelevisionParser;\n", "{\n \"Tesla\": {\n \"regex\": \"(?:Tesla/[0-9.]+|QtCarBrowser)\",\n \"device\": \"car browser\",\n \"models\": [\n {\n \"regex\": \"QtCarBrowser\",\n \"model\": \"Model S\"\n },\n {\n \"regex\": \"Tesla/[0-9.]+\",\n \"model\": \"\"\n }\n ]\n },\n \"MAC AUDIO\": {\n \"regex\": \"Mac Audio Spro\",\n \"device\": \"car browser\",\n \"models\": [\n {\n \"regex\": \"Spro\",\n \"model\": \"S Pro\"\n }\n ]\n },\n \"Topway\": {\n \"regex\": \"sp9853i_1h10_vmm\",\n \"device\": \"car browser\",\n \"models\": [\n {\n \"regex\": \"sp9853i_1h10_vmm\",\n \"model\": \"TS9\"\n }\n ]\n }\n}\n", "\"use strict\";\nvar __importDefault = (this && this.__importDefault) || function (mod) {\n return (mod && mod.__esModule) ? mod : { \"default\": mod };\n};\nObject.defineProperty(exports, \"__esModule\", { value: true });\nconst car_browsers_json_1 = __importDefault(require(\"../../fixtures/regexes/device/car_browsers.json\"));\nconst variable_replacement_1 = require(\"../../utils/variable-replacement\");\nconst user_agent_1 = require(\"../../utils/user-agent\");\nclass CarParser {\n constructor() {\n this.parse = (userAgent) => {\n const result = {\n type: \"\",\n brand: \"\",\n model: \"\"\n };\n for (const [brand, car] of Object.entries(car_browsers_json_1.default)) {\n const match = user_agent_1.userAgentParser(car.regex, userAgent);\n if (!match)\n continue;\n result.type = \"car\";\n result.brand = brand;\n for (const model of car.models) {\n const match = user_agent_1.userAgentParser(model.regex, userAgent);\n if (!match)\n continue;\n result.model = variable_replacement_1.variableReplacement(model.model, match).trim();\n }\n break;\n }\n return result;\n };\n }\n}\nexports.default = CarParser;\n", "{\n \"Archos\": {\n \"regex\": \"Archos.*GAMEPAD([2]?)\",\n \"device\": \"console\",\n \"model\": \"Gamepad $1\"\n },\n \"Microsoft\": {\n \"regex\": \"Xbox\",\n \"device\": \"console\",\n \"models\": [\n {\n \"regex\": \"Xbox Series X\",\n \"model\": \"Xbox Series X\"\n },\n {\n \"regex\": \"Xbox One X\",\n \"model\": \"Xbox One X\"\n },\n {\n \"regex\": \"Xbox One\",\n \"model\": \"Xbox One\"\n },\n {\n \"regex\": \"XBOX_ONE_ED\",\n \"model\": \"Xbox One S\"\n },\n {\n \"regex\": \"Xbox\",\n \"model\": \"Xbox 360\"\n }\n ]\n },\n \"Nintendo\": {\n \"regex\": \"Nintendo (([3]?DS[i]?)|Wii[U]?|Switch)\",\n \"device\": \"console\",\n \"model\": \"$1\"\n },\n \"OUYA\": {\n \"regex\": \"OUYA\",\n \"device\": \"console\",\n \"model\": \"OUYA\"\n },\n \"Sega\": {\n \"regex\": \"Dreamcast\",\n \"device\": \"console\",\n \"model\": \"Dreamcast\"\n },\n \"Sony\": {\n \"regex\": \"PlayStation (3|4 Pro|4|Portable|Vita)\",\n \"device\": \"console\",\n \"model\": \"PlayStation $1\"\n }\n}\n", "\"use strict\";\nvar __importDefault = (this && this.__importDefault) || function (mod) {\n return (mod && mod.__esModule) ? mod : { \"default\": mod };\n};\nObject.defineProperty(exports, \"__esModule\", { value: true });\nconst consoles_json_1 = __importDefault(require(\"../../fixtures/regexes/device/consoles.json\"));\nconst variable_replacement_1 = require(\"../../utils/variable-replacement\");\nconst user_agent_1 = require(\"../../utils/user-agent\");\nclass ConsoleParser {\n constructor() {\n this.parse = (userAgent) => {\n const result = {\n type: \"\",\n brand: \"\",\n model: \"\"\n };\n for (const [brand, gameConsole] of Object.entries(consoles_json_1.default)) {\n const match = user_agent_1.userAgentParser(gameConsole.regex, userAgent);\n if (!match)\n continue;\n result.type = gameConsole.device;\n result.brand = brand;\n if (\"model\" in gameConsole && gameConsole.model) {\n result.model = variable_replacement_1.variableReplacement(gameConsole.model, match).trim();\n }\n else if (\"models\" in gameConsole && gameConsole.models) {\n for (const model of gameConsole.models) {\n const modelMatch = user_agent_1.userAgentParser(model.regex, userAgent);\n if (!modelMatch)\n continue;\n result.model = variable_replacement_1.variableReplacement(model.model, modelMatch).trim();\n break;\n }\n }\n break;\n }\n return result;\n };\n }\n}\nexports.default = ConsoleParser;\n", "{\n \"Acer\": {\n \"regex\": \"FBMD/(?:Aspire E5-421G|Z5WAL|One S1003);\",\n \"device\": \"desktop\",\n \"models\": [\n {\n \"regex\": \"FBMD/Aspire E5-421G;\",\n \"model\": \"Aspire E5-421G\"\n },\n {\n \"regex\": \"FBMD/Z5WAL;\",\n \"model\": \"Aspire E5-511\"\n },\n {\n \"regex\": \"FBMD/One S1003;\",\n \"model\": \"One 10\"\n }\n ]\n },\n \"Asus\": {\n \"regex\": \"FBMD/(?:K50IN|K54L|T100HAN|T103HAF|UX360CAK|X550LB|X553MA|X555LN|X556UQK);\",\n \"device\": \"desktop\",\n \"models\": [\n {\n \"regex\": \"FBMD/K50IN;\",\n \"model\": \"K50IN\"\n },\n {\n \"regex\": \"FBMD/K54L;\",\n \"model\": \"K54L\"\n },\n {\n \"regex\": \"FBMD/T100HAN;\",\n \"model\": \"Transformer Book\"\n },\n {\n \"regex\": \"FBMD/T103HAF;\",\n \"model\": \"Transformer Mini\"\n },\n {\n \"regex\": \"FBMD/UX360CAK;\",\n \"model\": \"ZenBook Flip\"\n },\n {\n \"regex\": \"FBMD/X550LB;\",\n \"model\": \"X550LB\"\n },\n {\n \"regex\": \"FBMD/X553MA;\",\n \"model\": \"X553MA\"\n },\n {\n \"regex\": \"FBMD/X555LN;\",\n \"model\": \"X555LN\"\n },\n {\n \"regex\": \"FBMD/X556UQK;\",\n \"model\": \"X556UQK\"\n }\n ]\n },\n \"Dell\": {\n \"regex\": \"FBMD/(?:Latitude E4300|Inspiron 3541|XPS 15 95[35]0);\",\n \"device\": \"desktop\",\n \"models\": [\n {\n \"regex\": \"Latitude E4300\",\n \"model\": \"Latitude E4300\"\n },\n {\n \"regex\": \"Inspiron 3541\",\n \"model\": \"Inspiron 3541\"\n },\n {\n \"regex\": \"XPS 15 9530\",\n \"model\": \"XPS 15 9530\"\n },\n {\n \"regex\": \"XPS 15 9550\",\n \"model\": \"XPS 15 9550\"\n }\n ]\n },\n \"HP\": {\n \"regex\": \"FBMD/((?:Compaq|HP) |23-f364)\",\n \"device\": \"desktop\",\n \"models\": [\n {\n \"regex\": \"Compaq Presario CQ61 Notebook PC\",\n \"model\": \"Compaq Presario CQ61\"\n },\n {\n \"regex\": \"HP Pavilion x2 Detachable\",\n \"model\": \"Pavilion x2\"\n },\n {\n \"regex\": \"HP Laptop 15-bs0xx\",\n \"model\": \"15 Laptop PC\"\n },\n {\n \"regex\": \"HP ENVY x360 Convertible 15-bp0xx\",\n \"model\": \"ENVY x360 Convertible PC\"\n },\n {\n \"regex\": \"HP EliteBook (25[67]0p)\",\n \"model\": \"EliteBook $1\"\n },\n {\n \"regex\": \"HP ProBook (440 G5|6[35]60b)\",\n \"model\": \"ProBook $1\"\n },\n {\n \"regex\": \"HP Pavilion dv6 Notebook PC\",\n \"model\": \"Pavilion dv6\"\n },\n {\n \"regex\": \"HP Pavilion Notebook\",\n \"model\": \"Pavilion\"\n },\n {\n \"regex\": \"HP Spectre x360 Convertible\",\n \"model\": \"Spectre x360\"\n },\n {\n \"regex\": \"HP Pavilion All-in-One 24-r0xx\",\n \"model\": \"Pavilion 24-r0xx All-in-One Desktop PC\",\n \"device\": \"desktop\"\n },\n {\n \"regex\": \"23-f364\",\n \"model\": \"Pavilion TouchSmart 23-f364 All-in-One Desktop PC\",\n \"device\": \"desktop\"\n }\n ]\n },\n \"Lenovo\": {\n \"regex\": \"FBMD/(?:37021C5|80E5|80SM|80VR);\",\n \"device\": \"desktop\",\n \"models\": [\n {\n \"regex\": \"FBMD/37021C5;\",\n \"model\": \"ThinkPad Helix 3702\"\n },\n {\n \"regex\": \"FBMD/80E5;\",\n \"model\": \"G50-80\"\n },\n {\n \"regex\": \"FBMD/80SM;\",\n \"model\": \"Ideapad 310-15ISK\"\n },\n {\n \"regex\": \"FBMD/80VR;\",\n \"model\": \"Legion Y720\"\n }\n ]\n },\n \"Schneider\": {\n \"regex\": \"FBMD/SCL141CTP;\",\n \"device\": \"desktop\",\n \"model\": \"Notebook 14\\\" Cherry Trail\"\n },\n \"Thomson\": {\n \"regex\": \"FBMD/TH360R12.32CTW;\",\n \"device\": \"desktop\",\n \"model\": \"Prestige TH-360R12.32CTW\"\n },\n \"Toshiba\": {\n \"regex\": \"FBMD/Satellite \",\n \"device\": \"desktop\",\n \"models\": [\n {\n \"regex\": \"Satellite (A[25]00|C650|C855|L650|S855)\",\n \"model\": \"Satellite $1\"\n },\n {\n \"regex\": \"Satellite ([^;\\\\)]+);\",\n \"model\": \"Satellite $1\"\n }\n ]\n }\n}\n", "\"use strict\";\nvar __importDefault = (this && this.__importDefault) || function (mod) {\n return (mod && mod.__esModule) ? mod : { \"default\": mod };\n};\nObject.defineProperty(exports, \"__esModule\", { value: true });\nconst notebooks_json_1 = __importDefault(require(\"../../fixtures/regexes/device/notebooks.json\"));\nconst variable_replacement_1 = require(\"../../utils/variable-replacement\");\nconst user_agent_1 = require(\"../../utils/user-agent\");\nconst model_1 = require(\"../../utils/model\");\nclass NotebooksParser {\n constructor() {\n this.parse = (userAgent) => {\n const result = {\n type: \"\",\n brand: \"\",\n model: \"\"\n };\n if (!user_agent_1.userAgentParser(\"FBMD/\", userAgent)) {\n return result;\n }\n for (const [brand, notebook] of Object.entries(notebooks_json_1.default)) {\n const match = user_agent_1.userAgentParser(notebook.regex, userAgent);\n if (!match)\n continue;\n result.type = \"desktop\";\n result.brand = brand;\n if (\"model\" in notebook && notebook.model) {\n result.model = model_1.buildModel(variable_replacement_1.variableReplacement(notebook.model, match)).trim();\n }\n else if (\"models\" in notebook && notebook.models) {\n for (const model of notebook.models) {\n const match = user_agent_1.userAgentParser(model.regex, userAgent);\n if (!match)\n continue;\n result.model = variable_replacement_1.variableReplacement(model.model, match).trim();\n }\n }\n break;\n }\n return result;\n };\n }\n}\nexports.default = NotebooksParser;\n", "{\n \"Apple\": {\n \"regex\": \"(?:Apple-)?iPod\",\n \"device\": \"portable media player\",\n \"models\": [\n {\n \"regex\": \"(?:Apple-)?iPod1[C,]1\",\n \"model\": \"iPod Touch 1G\"\n },\n {\n \"regex\": \"(?:Apple-)?iPod2[C,]1\",\n \"model\": \"iPod Touch 2G\"\n },\n {\n \"regex\": \"(?:Apple-)?iPod3[C,]1\",\n \"model\": \"iPod Touch 3\"\n },\n {\n \"regex\": \"(?:Apple-)?iPod4[C,]1\",\n \"model\": \"iPod Touch 4\"\n },\n {\n \"regex\": \"(?:Apple-)?iPod5[C,]1\",\n \"model\": \"iPod Touch 5\"\n },\n {\n \"regex\": \"(?:Apple-)?iPod7[C,]1\",\n \"model\": \"iPod Touch 6\"\n },\n {\n \"regex\": \"(?:Apple-)?iPod\",\n \"model\": \"iPod Touch\"\n }\n ]\n },\n \"Cowon\": {\n \"regex\": \"COWON ([^;/]+) Build\",\n \"device\": \"portable media player\",\n \"model\": \"$1\"\n },\n \"Microsoft\": {\n \"regex\": \"Microsoft ZuneHD\",\n \"device\": \"portable media player\",\n \"model\": \"Zune HD\"\n },\n \"Panasonic\": {\n \"regex\": \"(SV-MV100)\",\n \"device\": \"portable media player\",\n \"model\": \"$1\"\n },\n \"Samsung\": {\n \"regex\": \"YP-(G[SIPB]?1|G[57]0|GB70D)\",\n \"device\": \"portable media player\",\n \"models\": [\n {\n \"regex\": \"YP-G[B]?1\",\n \"model\": \"Galaxy Player 4.0\"\n },\n {\n \"regex\": \"YP-G70\",\n \"model\": \"Galaxy Player 5.0\"\n },\n {\n \"regex\": \"YP-GS1\",\n \"model\": \"Galaxy Player 3.6\"\n },\n {\n \"regex\": \"YP-GI1\",\n \"model\": \"Galaxy Player 4.2\"\n },\n {\n \"regex\": \"YP-GP1\",\n \"model\": \"Galaxy Player 5.8\"\n },\n {\n \"regex\": \"YP-G50\",\n \"model\": \"Galaxy Player 50\"\n },\n {\n \"regex\": \"YP-GB70D\",\n \"model\": \"Galaxy Player 70 Plus\"\n }\n ]\n },\n \"Wizz\": {\n \"regex\": \"(DV-PTB1080)(?:[);/ ]|$)\",\n \"device\": \"portable media player\",\n \"model\": \"$1\"\n },\n \"SONOS\": {\n \"regex\": \"Sonos/.+\\\\((?:ZP.+)\\\\)|Sonos;\",\n \"device\": \"portable media player\",\n \"models\": [\n {\n \"regex\": \"\\\\((ZPS(?:[13569]|1[1-578]|2[03])|ZP90)\\\\)\",\n \"model\": \"$1\"\n },\n {\n \"regex\": \"Sonos;Play5;\",\n \"model\": \"Play:5\"\n },\n {\n \"regex\": \"Sonos;One;\",\n \"model\": \"One\"\n }\n ]\n }\n}\n", "\"use strict\";\nvar __importDefault = (this && this.__importDefault) || function (mod) {\n return (mod && mod.__esModule) ? mod : { \"default\": mod };\n};\nObject.defineProperty(exports, \"__esModule\", { value: true });\nconst portable_media_player_json_1 = __importDefault(require(\"../../fixtures/regexes/device/portable_media_player.json\"));\nconst variable_replacement_1 = require(\"../../utils/variable-replacement\");\nconst user_agent_1 = require(\"../../utils/user-agent\");\nclass PortableMediaPlayersParser {\n constructor() {\n this.parse = (userAgent) => {\n const result = {\n type: \"\",\n brand: \"\",\n model: \"\"\n };\n for (const [brand, portableMediaPlayer] of Object.entries(portable_media_player_json_1.default)) {\n const match = user_agent_1.userAgentParser(portableMediaPlayer.regex, userAgent);\n if (!match)\n continue;\n result.type = portableMediaPlayer.device;\n result.brand = brand;\n if (\"model\" in portableMediaPlayer && portableMediaPlayer.model) {\n result.model = variable_replacement_1.variableReplacement(portableMediaPlayer.model, match).trim();\n }\n else if (\"models\" in portableMediaPlayer && portableMediaPlayer.models) {\n for (const model of portableMediaPlayer.models) {\n const modelMatch = user_agent_1.userAgentParser(model.regex, userAgent);\n if (!modelMatch)\n continue;\n result.model = variable_replacement_1.variableReplacement(model.model, modelMatch).trim();\n break;\n }\n }\n break;\n }\n return result;\n };\n }\n}\nexports.default = PortableMediaPlayersParser;\n", "\"use strict\";\nvar __importDefault = (this && this.__importDefault) || function (mod) {\n return (mod && mod.__esModule) ? mod : { \"default\": mod };\n};\nObject.defineProperty(exports, \"__esModule\", { value: true });\nconst cameras_1 = __importDefault(require(\"./cameras\"));\nconst mobiles_1 = __importDefault(require(\"./mobiles\"));\nconst televisions_1 = __importDefault(require(\"./televisions\"));\nconst cars_1 = __importDefault(require(\"./cars\"));\nconst consoles_1 = __importDefault(require(\"./consoles\"));\nconst notebooks_1 = __importDefault(require(\"./notebooks\"));\nconst portable_media_players_1 = __importDefault(require(\"./portable-media-players\"));\nconst deviceParsers = [\n consoles_1.default,\n cars_1.default,\n cameras_1.default,\n televisions_1.default,\n portable_media_players_1.default,\n mobiles_1.default,\n notebooks_1.default\n];\nclass ClientParser {\n constructor() {\n this.parse = (userAgent) => {\n for (const Parser of deviceParsers) {\n const parser = new Parser();\n const device = parser.parse(userAgent);\n if (device.type !== \"\") {\n return device;\n }\n }\n return null;\n };\n }\n}\nexports.default = ClientParser;\n", "[\n {\n \"regex\": \"Grid OS (\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"GridOS\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"CaixaMagica\",\n \"name\": \"Caixa M\u00E1gica\",\n \"version\": \"\"\n },\n {\n \"regex\": \"Mageia; Linux\",\n \"name\": \"Mageia\",\n \"version\": \"\"\n },\n {\n \"regex\": \"(?:WH|WhaleTV/)(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"Whale OS\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Tizen[ /]?(\\\\d+[\\\\.\\\\d]+)?\",\n \"name\": \"Tizen\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Sailfish|Jolla\",\n \"name\": \"Sailfish OS\",\n \"version\": \"\"\n },\n {\n \"regex\": \"(?:Ali)?YunOS[ /]?(\\\\d+[\\\\.\\\\d]+)?\",\n \"name\": \"YunOS\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Windows Phone;FBSV/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"Windows Phone\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"(?:Windows Phone (?:OS)?|wds)[ /]?(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"Windows Phone\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"XBLWP7|Windows Phone\",\n \"name\": \"Windows Phone\",\n \"version\": \"\"\n },\n {\n \"regex\": \"Windows CE(?: (\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Windows CE\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"(?:IEMobile|Windows ?Mobile)(?: (\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Windows Mobile\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Windows NT 6.2; ARM;\",\n \"name\": \"Windows RT\",\n \"version\": \"\"\n },\n {\n \"regex\": \"Windows NT 6.3; ARM;\",\n \"name\": \"Windows RT\",\n \"version\": \"8.1\"\n },\n {\n \"regex\": \"Windows IoT 10.0\",\n \"name\": \"Windows IoT\",\n \"version\": \"10\"\n },\n {\n \"regex\": \"KAIOS(?:/(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"KaiOS\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"RazoDroiD(?: v(\\\\d+[\\\\.\\\\d]*))?\",\n \"name\": \"RazoDroiD\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"MildWild(?: CM-(\\\\d+[\\\\.\\\\d]*))?\",\n \"name\": \"MildWild\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"CyanogenMod(?:[\\\\-/](?:CM)?(\\\\d+[\\\\.\\\\d]*))?\",\n \"name\": \"CyanogenMod\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"(?:.*_)?MocorDroid(?:(\\\\d+[\\\\.\\\\d]*))?\",\n \"name\": \"MocorDroid\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Fire OS(?:/(\\\\d+[\\\\.\\\\d]*))?\",\n \"name\": \"Fire OS\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"AFTSO001\",\n \"name\": \"Fire OS\",\n \"version\": \"7\"\n },\n {\n \"regex\": \"FydeOS\",\n \"name\": \"FydeOS\",\n \"version\": \"\"\n },\n {\n \"regex\": \"(?:Podbean|Podimo)(?:.+)?/Android\",\n \"name\": \"Android\",\n \"version\": \"\"\n },\n {\n \"regex\": \"Pinterest for Android/.+; (\\\\d(?:[\\\\d\\\\.]+)?)\\\\)$\",\n \"name\": \"Android\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Android; (\\\\d+[\\\\.\\\\d]*); Mobile;\",\n \"name\": \"Android\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"[ ]([\\\\d\\\\.]+)\\\\) AppleWebKit.*ROBLOX Android App\",\n \"name\": \"Android\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"(?:(?:Orca-)?Android|Adr)[ /]?(?:[a-z]+ )?(\\\\d+[\\\\.\\\\d]*)\",\n \"name\": \"Android\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"(?:Allview_TX1_Quasar|Cosmote_My_mini_Tab) (\\\\d+[\\\\.\\\\d]*)\",\n \"name\": \"Android\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Android ?(?:jelly bean|Kit Kat|S.O. Ginger Bread|The FireCyano|:) (\\\\d+[\\\\.\\\\d]*)\",\n \"name\": \"Android\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"(?:Orca-Android|FB4A).*FBSV/(\\\\d+[\\\\.\\\\d]*);\",\n \"name\": \"Android\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \" Adr |Android|Silk-Accelerated=[a-z]{4,5}\",\n \"name\": \"Android\",\n \"version\": \"\"\n },\n {\n \"regex\": \"(?:TwitterAndroid).*[ /](?:[a-z]+ )?(\\\\d+[\\\\.\\\\d]*)\",\n \"name\": \"Android\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"BeyondPod|AntennaPod|Podkicker|DoggCatcher|Player FM|okhttp|Podcatcher Deluxe|.+K_?Android_?TV_|Sonos/.+\\\\(ACR_\",\n \"name\": \"Android\",\n \"version\": \"\"\n },\n {\n \"regex\": \"Linux; Andr0id[; ](\\\\d+[\\\\.\\\\d]*)\",\n \"name\": \"Android\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Linux; diordnA[; ](\\\\d+[\\\\.\\\\d]*)\",\n \"name\": \"Android\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"AmigaOS[ ]?(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"AmigaOS\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"AmigaOS|AmigaVoyager|Amiga-AWeb\",\n \"name\": \"AmigaOS\",\n \"version\": \"\"\n },\n {\n \"regex\": \"ThreadX(?:/(\\\\d+[\\\\.\\\\d]*))?\",\n \"name\": \"ThreadX\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Nucleus(?:(?: |/v?)(\\\\d+[\\\\.\\\\d]*))?\",\n \"name\": \"MTK / Nucleus\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"MTK(?:(?: |/v?)(\\\\d+[\\\\.\\\\d]*))?\",\n \"name\": \"MTK / Nucleus\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"MRE/(\\\\d+)\\\\.(\\\\d+).*;MAUI\",\n \"name\": \"MRE\",\n \"version\": \"$1.$2\"\n },\n {\n \"regex\": \"dvkbuntu\",\n \"name\": \"DVKBuntu\",\n \"version\": \"\"\n },\n {\n \"regex\": \"Helio/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"Lumin OS\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"HasCodingOs (\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"HasCodingOS\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"PCLinuxOS/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"PCLinuxOS\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"(Ordissimo|webissimo3)\",\n \"name\": \"Ordissimo\",\n \"version\": \"\"\n },\n {\n \"regex\": \"(?:Win|Sistema )Fenix\",\n \"name\": \"Fenix\",\n \"version\": \"\"\n },\n {\n \"regex\": \"TOS; Linux\",\n \"name\": \"TmaxOS\",\n \"version\": \"\"\n },\n {\n \"regex\": \"Maemo\",\n \"name\": \"Maemo\",\n \"version\": \"\"\n },\n {\n \"regex\": \"Arch ?Linux(?:[ /\\\\-](\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Arch Linux\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"VectorLinux(?: package)?(?:[ /\\\\-](\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"VectorLinux\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Linux; .*((?:Debian|Knoppix|Mint|Ubuntu|Kubuntu|Xubuntu|Lubuntu|Fedora|Red Hat|Mandriva|Gentoo|Sabayon|Slackware|SUSE|CentOS|BackTrack))[ /](\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"$1\",\n \"version\": \"$2\"\n },\n {\n \"regex\": \"Deepin[ /](\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"Deepin\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"(Debian|Knoppix|Mint(?! Browser)|Ubuntu|Kubuntu|Xubuntu|Lubuntu|Fedora|Red Hat|Mandriva|Gentoo|Sabayon|Slackware|SUSE|CentOS|BackTrack|Freebox)(?:(?: Enterprise)? Linux)?(?:[ /\\\\-](\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"$1\",\n \"version\": \"$2\"\n },\n {\n \"regex\": \"OS ROSA; Linux\",\n \"name\": \"Rosa\",\n \"version\": \"\"\n },\n {\n \"regex\": \"WEBOS(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"webOS\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Web0S; Linux/SmartTV.+Chrome/68\",\n \"name\": \"webOS\",\n \"version\": \"5\"\n },\n {\n \"regex\": \"Web0S; Linux/SmartTV.+Chrome/53\",\n \"name\": \"webOS\",\n \"version\": \"4\"\n },\n {\n \"regex\": \"Web0S; Linux/SmartTV.+Chrome/38\",\n \"name\": \"webOS\",\n \"version\": \"3\"\n },\n {\n \"regex\": \"Web0S; Linux/SmartTV.+Safari/538\",\n \"name\": \"webOS\",\n \"version\": \"2\"\n },\n {\n \"regex\": \"Web0S; Linux/SmartTV.+Safari/537\",\n \"name\": \"webOS\",\n \"version\": \"1\"\n },\n {\n \"regex\": \"(?:Web0S; .*WEBOS|webOS|web0S|Palm webOS|hpwOS)(?:[/]?(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"webOS\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"(?:PalmOS|Palm OS)(?:[/ ](\\\\d+[\\\\.\\\\d]+))?|Palm\",\n \"name\": \"palmOS\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Xiino(?:.*v\\\\. (\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"palmOS\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"MorphOS(?:[ /](\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"MorphOS\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"FBW.+FBSV/(\\\\d+[\\\\.\\\\d]*);\",\n \"name\": \"Windows\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"mingw32\",\n \"name\": \"Windows\",\n \"version\": \"\"\n },\n {\n \"regex\": \"Windows/(\\\\d+\\\\.\\\\d+)\",\n \"name\": \"Windows\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"CYGWIN_NT-10.0|Windows NT 10.0|Windows 10\",\n \"name\": \"Windows\",\n \"version\": \"10\"\n },\n {\n \"regex\": \"CYGWIN_NT-6.4|Windows NT 6.4|Windows 10|win10\",\n \"name\": \"Windows\",\n \"version\": \"10\"\n },\n {\n \"regex\": \"CYGWIN_NT-6.3|Windows NT 6.3|Windows 8.1\",\n \"name\": \"Windows\",\n \"version\": \"8.1\"\n },\n {\n \"regex\": \"CYGWIN_NT-6.2|Windows NT 6.2|Windows 8\",\n \"name\": \"Windows\",\n \"version\": \"8\"\n },\n {\n \"regex\": \"CYGWIN_NT-6.1|Windows NT 6.1|Windows 7|win7\",\n \"name\": \"Windows\",\n \"version\": \"7\"\n },\n {\n \"regex\": \"CYGWIN_NT-6.0|Windows NT 6.0|Windows Vista\",\n \"name\": \"Windows\",\n \"version\": \"Vista\"\n },\n {\n \"regex\": \"CYGWIN_NT-5.2|Windows NT 5.2|Windows Server 2003 / XP x64\",\n \"name\": \"Windows\",\n \"version\": \"Server 2003\"\n },\n {\n \"regex\": \"CYGWIN_NT-5.1|Windows NT 5.1|Windows XP\",\n \"name\": \"Windows\",\n \"version\": \"XP\"\n },\n {\n \"regex\": \"CYGWIN_NT-5.0|Windows NT 5.0|Windows 2000\",\n \"name\": \"Windows\",\n \"version\": \"2000\"\n },\n {\n \"regex\": \"CYGWIN_NT-4.0|Windows NT 4.0|WinNT|Windows NT\",\n \"name\": \"Windows\",\n \"version\": \"NT\"\n },\n {\n \"regex\": \"CYGWIN_ME-4.90|Win 9x 4.90|Windows ME\",\n \"name\": \"Windows\",\n \"version\": \"ME\"\n },\n {\n \"regex\": \"CYGWIN_98-4.10|Win98|Windows 98\",\n \"name\": \"Windows\",\n \"version\": \"98\"\n },\n {\n \"regex\": \"CYGWIN_95-4.0|Win32|Win95|Windows 95|Windows_95\",\n \"name\": \"Windows\",\n \"version\": \"95\"\n },\n {\n \"regex\": \"Windows 3.1\",\n \"name\": \"Windows\",\n \"version\": \"3.1\"\n },\n {\n \"regex\": \"Windows\",\n \"name\": \"Windows\",\n \"version\": \"\"\n },\n {\n \"regex\": \"Haiku\",\n \"name\": \"Haiku OS\",\n \"version\": \"\"\n },\n {\n \"regex\": \"(?:Watch1,[12]/|Watch OS,|watchOS[ /])(\\\\d+[\\\\.\\\\d]*)\",\n \"name\": \"watchOS\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"iPad/([89]|1[012]).(\\\\d+[\\\\.\\\\d]*)\",\n \"name\": \"iOS\",\n \"version\": \"$1.$2\"\n },\n {\n \"regex\": \"Pinterest for iOS/.+; (\\\\d(?:[\\\\d\\\\.]+)?)\\\\)$\",\n \"name\": \"iOS\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"CFNetwork/.+ Darwin/20.4.0(?!.*x86_64)\",\n \"name\": \"iOS\",\n \"version\": \"14.5\"\n },\n {\n \"regex\": \"CFNetwork/.+ Darwin/20.3.0(?!.*x86_64)\",\n \"name\": \"iOS\",\n \"version\": \"14.4\"\n },\n {\n \"regex\": \"CFNetwork/.+ Darwin/20.2.0(?!.*x86_64)\",\n \"name\": \"iOS\",\n \"version\": \"14.3\"\n },\n {\n \"regex\": \"CFNetwork/.+ Darwin/20.1.0(?!.*x86_64)\",\n \"name\": \"iOS\",\n \"version\": \"14.2\"\n },\n {\n \"regex\": \"CFNetwork/.+ Darwin/20.0.0(?!.*x86_64)\",\n \"name\": \"iOS\",\n \"version\": \"14.0\"\n },\n {\n \"regex\": \"CFNetwork/.+ Darwin/19.6.0(?!.*x86_64)\",\n \"name\": \"iOS\",\n \"version\": \"13.6\"\n },\n {\n \"regex\": \"CFNetwork/.+ Darwin/19.5.0(?!.*x86_64)\",\n \"name\": \"iOS\",\n \"version\": \"13.5\"\n },\n {\n \"regex\": \"CFNetwork/.+ Darwin/19.4.0(?!.*x86_64)\",\n \"name\": \"iOS\",\n \"version\": \"13.4\"\n },\n {\n \"regex\": \"CFNetwork/.+ Darwin/19.3.0(?!.*x86_64)\",\n \"name\": \"iOS\",\n \"version\": \"13.3.1\"\n },\n {\n \"regex\": \"CFNetwork/.+ Darwin/19.2.0(?!.*x86_64)\",\n \"name\": \"iOS\",\n \"version\": \"13.3\"\n },\n {\n \"regex\": \"CFNetwork/.+ Darwin/19.0.0(?!.*x86_64)\",\n \"name\": \"iOS\",\n \"version\": \"13.0\"\n },\n {\n \"regex\": \"CFNetwork/.+ Darwin/18.7.0(?!.*x86_64)\",\n \"name\": \"iOS\",\n \"version\": \"12.4\"\n },\n {\n \"regex\": \"CFNetwork/.+ Darwin/18.6.0(?!.*x86_64)\",\n \"name\": \"iOS\",\n \"version\": \"12.3\"\n },\n {\n \"regex\": \"CFNetwork/.+ Darwin/18.5.0(?!.*x86_64)\",\n \"name\": \"iOS\",\n \"version\": \"12.2\"\n },\n {\n \"regex\": \"CFNetwork/.+ Darwin/18.2.0(?!.*x86_64)\",\n \"name\": \"iOS\",\n \"version\": \"12.1\"\n },\n {\n \"regex\": \"CFNetwork/.+ Darwin/18.0.0(?!.*x86_64)\",\n \"name\": \"iOS\",\n \"version\": \"12.0\"\n },\n {\n \"regex\": \"CFNetwork/.+ Darwin/17.7.0(?!.*x86_64)\",\n \"name\": \"iOS\",\n \"version\": \"11.4.1\"\n },\n {\n \"regex\": \"CFNetwork/.+ Darwin/17.6.0(?!.*x86_64)\",\n \"name\": \"iOS\",\n \"version\": \"11.4\"\n },\n {\n \"regex\": \"CFNetwork/889(?!.*x86_64)\",\n \"name\": \"iOS\",\n \"version\": \"11.1\"\n },\n {\n \"regex\": \"CFNetwork/887(?!.*x86_64)\",\n \"name\": \"iOS\",\n \"version\": \"11.0\"\n },\n {\n \"regex\": \"CFNetwork/811(?!.*x86_64)\",\n \"name\": \"iOS\",\n \"version\": \"10.3\"\n },\n {\n \"regex\": \"CFNetwork/808\\\\.3\",\n \"name\": \"iOS\",\n \"version\": \"10.3\"\n },\n {\n \"regex\": \"CFNetwork/808\\\\.2\",\n \"name\": \"iOS\",\n \"version\": \"10.2\"\n },\n {\n \"regex\": \"CFNetwork/808\\\\.1\",\n \"name\": \"iOS\",\n \"version\": \"10.1\"\n },\n {\n \"regex\": \"CFNetwork/808\\\\.0\",\n \"name\": \"iOS\",\n \"version\": \"10.0\"\n },\n {\n \"regex\": \"CFNetwork/808\",\n \"name\": \"iOS\",\n \"version\": \"10\"\n },\n {\n \"regex\": \"CFNetwork/758\\\\.4\\\\.3\",\n \"name\": \"iOS\",\n \"version\": \"9.3.2\"\n },\n {\n \"regex\": \"CFNetwork/758\\\\.3\\\\.15\",\n \"name\": \"iOS\",\n \"version\": \"9.3\"\n },\n {\n \"regex\": \"CFNetwork/758\\\\.2\\\\.[78]\",\n \"name\": \"iOS\",\n \"version\": \"9.2\"\n },\n {\n \"regex\": \"CFNetwork/758\\\\.1\\\\.6\",\n \"name\": \"iOS\",\n \"version\": \"9.1\"\n },\n {\n \"regex\": \"CFNetwork/758\\\\.0\\\\.2\",\n \"name\": \"iOS\",\n \"version\": \"9.0\"\n },\n {\n \"regex\": \"CFNetwork/711\\\\.5\\\\.6\",\n \"name\": \"iOS\",\n \"version\": \"8.4.1\"\n },\n {\n \"regex\": \"CFNetwork/711\\\\.4\\\\.6\",\n \"name\": \"iOS\",\n \"version\": \"8.4\"\n },\n {\n \"regex\": \"CFNetwork/711\\\\.3\\\\.18\",\n \"name\": \"iOS\",\n \"version\": \"8.3\"\n },\n {\n \"regex\": \"CFNetwork/711\\\\.2\\\\.23\",\n \"name\": \"iOS\",\n \"version\": \"8.2\"\n },\n {\n \"regex\": \"CFNetwork/711\\\\.1\\\\.1[26]\",\n \"name\": \"iOS\",\n \"version\": \"8.1\"\n },\n {\n \"regex\": \"CFNetwork/711\\\\.0\\\\.6\",\n \"name\": \"iOS\",\n \"version\": \"8.0\"\n },\n {\n \"regex\": \"CFNetwork/672\\\\.1\",\n \"name\": \"iOS\",\n \"version\": \"7.1\"\n },\n {\n \"regex\": \"CFNetwork/672\\\\.0\",\n \"name\": \"iOS\",\n \"version\": \"7.0\"\n },\n {\n \"regex\": \"CFNetwork/609\\\\.1\",\n \"name\": \"iOS\",\n \"version\": \"6.1\"\n },\n {\n \"regex\": \"CFNetwork/60[29]\",\n \"name\": \"iOS\",\n \"version\": \"6.0\"\n },\n {\n \"regex\": \"CFNetwork/548\\\\.1\",\n \"name\": \"iOS\",\n \"version\": \"5.1\"\n },\n {\n \"regex\": \"CFNetwork/548\\\\.0\",\n \"name\": \"iOS\",\n \"version\": \"5.0\"\n },\n {\n \"regex\": \"CFNetwork/485\\\\.13\",\n \"name\": \"iOS\",\n \"version\": \"4.3\"\n },\n {\n \"regex\": \"CFNetwork/485\\\\.12\",\n \"name\": \"iOS\",\n \"version\": \"4.2\"\n },\n {\n \"regex\": \"CFNetwork/485\\\\.10\",\n \"name\": \"iOS\",\n \"version\": \"4.1\"\n },\n {\n \"regex\": \"CFNetwork/485\\\\.2\",\n \"name\": \"iOS\",\n \"version\": \"4.0\"\n },\n {\n \"regex\": \"CFNetwork/467\\\\.12\",\n \"name\": \"iOS\",\n \"version\": \"3.2\"\n },\n {\n \"regex\": \"CFNetwork/459\",\n \"name\": \"iOS\",\n \"version\": \"3.1\"\n },\n {\n \"regex\": \"iPhone/(\\\\d+[\\\\.\\\\d]*) hw/\",\n \"name\": \"iOS\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"iOS(\\\\d+\\\\.\\\\d+\\\\.\\\\d+)\",\n \"name\": \"iOS\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"iOS(\\\\d+)\\\\.(\\\\d+)0\",\n \"name\": \"iOS\",\n \"version\": \"$1.$2\"\n },\n {\n \"regex\": \"iPhone OS ([0-9]{1})([0-9]{1})([0-9]{1})\",\n \"name\": \"iOS\",\n \"version\": \"$1.$2.$3\"\n },\n {\n \"regex\": \"(?:CPU OS|iPh(?:one)?[ _]OS|iOS)[ _/](\\\\d+(?:[_\\\\.]\\\\d+)*)\",\n \"name\": \"iOS\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"(?:FBIOS|Messenger(?:Lite)?ForiOS).*FBSV/ ?(\\\\d+[\\\\.\\\\d]*);\",\n \"name\": \"iOS\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"(?:Apple-)?(?:iPhone|iPad|iPod)(?:.*Mac OS X.*Version/(\\\\d+\\\\.\\\\d+)|; Opera)?\",\n \"name\": \"iOS\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"(?:Podcasts/(?:[\\\\d\\\\.]+)|Instacast(?:HD)?/(?:\\\\d\\\\.[\\\\d\\\\.abc]+)|Pocket Casts, iOS|\\\\(iOS\\\\)|iOS; Opera|Overcast|Castro|Podcat|iCatcher|RSSRadio/|MobileSafari/)(?!.*x86_64)\",\n \"name\": \"iOS\",\n \"version\": \"\"\n },\n {\n \"regex\": \"iTunes-(iPod|iPad|iPhone)/(?:[\\\\d\\\\.]+)\",\n \"name\": \"iOS\",\n \"version\": \"\"\n },\n {\n \"regex\": \"iOS/Version ([\\\\d\\\\.]+)\",\n \"name\": \"iOS\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Sonos/.+\\\\(ICRU_\",\n \"name\": \"iOS\",\n \"version\": \"\"\n },\n {\n \"regex\": \"CFNetwork/.+ Darwin/20.2.0.*(x86_64)|(x86_64-apple-)?darwin20.2.0\",\n \"name\": \"Mac\",\n \"version\": \"11.1\"\n },\n {\n \"regex\": \"CFNetwork/.+ Darwin/20.[01].0.*(x86_64)|(x86_64-apple-)?darwin20.[01].0\",\n \"name\": \"Mac\",\n \"version\": \"11.0\"\n },\n {\n \"regex\": \"CFNetwork/.+ Darwin/19.6.0.*(x86_64)|(x86_64-apple-)?darwin19.6.0\",\n \"name\": \"Mac\",\n \"version\": \"10.15.6\"\n },\n {\n \"regex\": \"CFNetwork/.+ Darwin/19.5.0.*(x86_64)|(x86_64-apple-)?darwin19.5.0\",\n \"name\": \"Mac\",\n \"version\": \"10.15.5\"\n },\n {\n \"regex\": \"CFNetwork/.+ Darwin/19.4.0.*(x86_64)|(x86_64-apple-)?darwin19.4.0\",\n \"name\": \"Mac\",\n \"version\": \"10.15.4\"\n },\n {\n \"regex\": \"CFNetwork/.+ Darwin/19.3.0.*(x86_64)|(x86_64-apple-)?darwin19.3.0\",\n \"name\": \"Mac\",\n \"version\": \"10.15.3\"\n },\n {\n \"regex\": \"CFNetwork/.+ Darwin/19.2.0.*(x86_64)|(x86_64-apple-)?darwin19.2.0\",\n \"name\": \"Mac\",\n \"version\": \"10.15.2\"\n },\n {\n \"regex\": \"CFNetwork/(?:108[258]|109[18]|1103).*(x86_64)\",\n \"name\": \"Mac\",\n \"version\": \"10.15\"\n },\n {\n \"regex\": \"CFNetwork/.+ Darwin/18.2.0.*(x86_64)|(x86_64-apple-)?darwin18.2.0\",\n \"name\": \"Mac\",\n \"version\": \"10.14.1\"\n },\n {\n \"regex\": \"CFNetwork/(?:96[29]|97[14568]).*(x86_64)\",\n \"name\": \"Mac\",\n \"version\": \"10.14\"\n },\n {\n \"regex\": \"CFNetwork/.+ Darwin/17.7.0.*(x86_64)|(x86_64-apple-)?darwin17.7.0\",\n \"name\": \"Mac\",\n \"version\": \"10.13.6\"\n },\n {\n \"regex\": \"CFNetwork/.+ Darwin/17.6.0.*(x86_64)|(x86_64-apple-)?darwin17.6.0\",\n \"name\": \"Mac\",\n \"version\": \"10.13.5\"\n },\n {\n \"regex\": \"CFNetwork/.+ Darwin/17.5.0.*(x86_64)|(x86_64-apple-)?darwin17.5.0\",\n \"name\": \"Mac\",\n \"version\": \"10.13.4\"\n },\n {\n \"regex\": \"CFNetwork/.+ Darwin/17.4.0.*(x86_64)|(x86_64-apple-)?darwin17.4.0\",\n \"name\": \"Mac\",\n \"version\": \"10.13.3\"\n },\n {\n \"regex\": \"CFNetwork/.+ Darwin/17.3.0.*(x86_64)|(x86_64-apple-)?darwin17.3.0\",\n \"name\": \"Mac\",\n \"version\": \"10.13.2\"\n },\n {\n \"regex\": \"CFNetwork/.+ Darwin/17.2.0.*(x86_64)|(x86_64-apple-)?darwin17.2.0\",\n \"name\": \"Mac\",\n \"version\": \"10.13.1\"\n },\n {\n \"regex\": \"CFNetwork/(?:887|889|893|897|901|902).*(x86_64)\",\n \"name\": \"Mac\",\n \"version\": \"10.13\"\n },\n {\n \"regex\": \"CFNetwork/.+ Darwin/16.7.0.*(x86_64)|(x86_64-apple-)?darwin16.7.0\",\n \"name\": \"Mac\",\n \"version\": \"10.12.6\"\n },\n {\n \"regex\": \"CFNetwork/.+ Darwin/16.6.0.*(x86_64)|(x86_64-apple-)?darwin16.6.0\",\n \"name\": \"Mac\",\n \"version\": \"10.12.5\"\n },\n {\n \"regex\": \"CFNetwork/.+ Darwin/16.5.0.*(x86_64)|(x86_64-apple-)?darwin16.5.0\",\n \"name\": \"Mac\",\n \"version\": \"10.12.4\"\n },\n {\n \"regex\": \"CFNetwork/.+ Darwin/16.3.0.*(x86_64)|(x86_64-apple-)?darwin16.3.0\",\n \"name\": \"Mac\",\n \"version\": \"10.12.3\"\n },\n {\n \"regex\": \"CFNetwork/.+ Darwin/16.2.0.*(x86_64)|(x86_64-apple-)?darwin16.2.0\",\n \"name\": \"Mac\",\n \"version\": \"10.12.2\"\n },\n {\n \"regex\": \"CFNetwork/.+ Darwin/16.1.0.*(x86_64)|(x86_64-apple-)?darwin16.1.0\",\n \"name\": \"Mac\",\n \"version\": \"10.12.1\"\n },\n {\n \"regex\": \"CFNetwork/811.*(x86_64)\",\n \"name\": \"Mac\",\n \"version\": \"10.12\"\n },\n {\n \"regex\": \"CFNetwork/807\",\n \"name\": \"Mac\",\n \"version\": \"10.12\"\n },\n {\n \"regex\": \"CFNetwork/.+ Darwin/15.6.0.*(x86_64)|(x86_64-apple-)?darwin15.6.0\",\n \"name\": \"Mac\",\n \"version\": \"10.11.6\"\n },\n {\n \"regex\": \"CFNetwork/.+ Darwin/15.5.0.*(x86_64)|(x86_64-apple-)?darwin15.5.0\",\n \"name\": \"Mac\",\n \"version\": \"10.11.5\"\n },\n {\n \"regex\": \"CFNetwork/.+ Darwin/15.4.0.*(x86_64)|(x86_64-apple-)?darwin15.4.0\",\n \"name\": \"Mac\",\n \"version\": \"10.11.4\"\n },\n {\n \"regex\": \"CFNetwork/.+ Darwin/15.3.0.*(x86_64)|(x86_64-apple-)?darwin15.3.0\",\n \"name\": \"Mac\",\n \"version\": \"10.11.3\"\n },\n {\n \"regex\": \"CFNetwork/.+ Darwin/15.2.0.*(x86_64)|(x86_64-apple-)?darwin15.2.0\",\n \"name\": \"Mac\",\n \"version\": \"10.11.2\"\n },\n {\n \"regex\": \"CFNetwork/760\",\n \"name\": \"Mac\",\n \"version\": \"10.11\"\n },\n {\n \"regex\": \"CFNetwork/.+ Darwin/14.5.0.*(x86_64)|(x86_64-apple-)?darwin14.5.0\",\n \"name\": \"Mac\",\n \"version\": \"10.10.5\"\n },\n {\n \"regex\": \"CFNetwork/.+ Darwin/14.4.0.*(x86_64)|(x86_64-apple-)?darwin14.4.0\",\n \"name\": \"Mac\",\n \"version\": \"10.10.4\"\n },\n {\n \"regex\": \"CFNetwork/.+ Darwin/14.3.0.*(x86_64)|(x86_64-apple-)?darwin14.3.0\",\n \"name\": \"Mac\",\n \"version\": \"10.10.3\"\n },\n {\n \"regex\": \"CFNetwork/.+ Darwin/14.1.0.*(x86_64)|(x86_64-apple-)?darwin14.1.0\",\n \"name\": \"Mac\",\n \"version\": \"10.10.2\"\n },\n {\n \"regex\": \"CFNetwork/720\",\n \"name\": \"Mac\",\n \"version\": \"10.10\"\n },\n {\n \"regex\": \"CFNetwork/.+ Darwin/13.4.0.*(x86_64)|(x86_64-apple-)?darwin13.4.0\",\n \"name\": \"Mac\",\n \"version\": \"10.9.5\"\n },\n {\n \"regex\": \"CFNetwork/.+ Darwin/13.3.0.*(x86_64)|(x86_64-apple-)?darwin13.3.0\",\n \"name\": \"Mac\",\n \"version\": \"10.9.4\"\n },\n {\n \"regex\": \"CFNetwork/.+ Darwin/13.2.0.*(x86_64)|(x86_64-apple-)?darwin13.2.0\",\n \"name\": \"Mac\",\n \"version\": \"10.9.3\"\n },\n {\n \"regex\": \"CFNetwork/.+ Darwin/13.1.0.*(x86_64)|(x86_64-apple-)?darwin13.1.0\",\n \"name\": \"Mac\",\n \"version\": \"10.9.2\"\n },\n {\n \"regex\": \"CFNetwork/673\",\n \"name\": \"Mac\",\n \"version\": \"10.9\"\n },\n {\n \"regex\": \"CFNetwork/.+ Darwin/12.5.0.*(x86_64)|(x86_64-apple-)?darwin12.5.0\",\n \"name\": \"Mac\",\n \"version\": \"10.8.5\"\n },\n {\n \"regex\": \"CFNetwork/.+ Darwin/12.4.0.*(x86_64)|(x86_64-apple-)?darwin12.4.0\",\n \"name\": \"Mac\",\n \"version\": \"10.8.4\"\n },\n {\n \"regex\": \"CFNetwork/.+ Darwin/12.3.0.*(x86_64)|(x86_64-apple-)?darwin12.3.0\",\n \"name\": \"Mac\",\n \"version\": \"10.8.3\"\n },\n {\n \"regex\": \"CFNetwork/.+ Darwin/12.2.0.*(x86_64)|(x86_64-apple-)?darwin12.2.0\",\n \"name\": \"Mac\",\n \"version\": \"10.8.2\"\n },\n {\n \"regex\": \"CFNetwork/.+ Darwin/12.1.0.*(x86_64)|(x86_64-apple-)?darwin12.1.0\",\n \"name\": \"Mac\",\n \"version\": \"10.8.1\"\n },\n {\n \"regex\": \"CFNetwork/596\",\n \"name\": \"Mac\",\n \"version\": \"10.8\"\n },\n {\n \"regex\": \"CFNetwork/.+ Darwin/11.5.0.*(x86_64)|(x86_64-apple-)?darwin11.5.0\",\n \"name\": \"Mac\",\n \"version\": \"10.7.5\"\n },\n {\n \"regex\": \"CFNetwork/.+ Darwin/11.4.0.*(x86_64)|(x86_64-apple-)?darwin11.4.0\",\n \"name\": \"Mac\",\n \"version\": \"10.7.4\"\n },\n {\n \"regex\": \"CFNetwork/.+ Darwin/11.3.0.*(x86_64)|(x86_64-apple-)?darwin11.3.0\",\n \"name\": \"Mac\",\n \"version\": \"10.7.3\"\n },\n {\n \"regex\": \"CFNetwork/.+ Darwin/11.2.0.*(x86_64)|(x86_64-apple-)?darwin11.2.0\",\n \"name\": \"Mac\",\n \"version\": \"10.7.2\"\n },\n {\n \"regex\": \"CFNetwork/.+ Darwin/11.1.0.*(x86_64)|(x86_64-apple-)?darwin11.1.0\",\n \"name\": \"Mac\",\n \"version\": \"10.7.1\"\n },\n {\n \"regex\": \"CFNetwork/520\",\n \"name\": \"Mac\",\n \"version\": \"10.7\"\n },\n {\n \"regex\": \"CFNetwork/454\",\n \"name\": \"Mac\",\n \"version\": \"10.6\"\n },\n {\n \"regex\": \"CFNetwork/(?:438|422|339|330|221|220|217)\",\n \"name\": \"Mac\",\n \"version\": \"10.5\"\n },\n {\n \"regex\": \"CFNetwork/12[89]\",\n \"name\": \"Mac\",\n \"version\": \"10.4\"\n },\n {\n \"regex\": \"CFNetwork/1\\\\.2\",\n \"name\": \"Mac\",\n \"version\": \"10.3\"\n },\n {\n \"regex\": \"CFNetwork/1\\\\.1\",\n \"name\": \"Mac\",\n \"version\": \"10.2\"\n },\n {\n \"regex\": \"Mac[ +]OS[ +]?X(?:[ /](?:Version )?(\\\\d+(?:[_\\\\.]\\\\d+)+))?\",\n \"name\": \"Mac\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Mac (?:OS/)?(\\\\d+(?:[_\\\\.]\\\\d+)+)\",\n \"name\": \"Mac\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"macOS/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"Mac\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Darwin|Macintosh|Mac_PowerPC|PPC|Mac PowerPC|iMac|MacBook|macOS|Sonos/.+\\\\(MDCR_\",\n \"name\": \"Mac\",\n \"version\": \"\"\n },\n {\n \"regex\": \"SeewoOS x86_64 (\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"SeewoOS\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"CrOS [a-z0-9_]+ .* Chrome/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"Chrome OS\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"(?:BB10;.+Version|Black[Bb]erry[0-9a-z]+|Black[Bb]erry.+Version)/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"BlackBerry OS\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"RIM Tablet OS (\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"BlackBerry Tablet OS\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"RIM Tablet OS|QNX|Play[Bb]ook\",\n \"name\": \"BlackBerry Tablet OS\",\n \"version\": \"\"\n },\n {\n \"regex\": \"BlackBerry\",\n \"name\": \"BlackBerry OS\",\n \"version\": \"\"\n },\n {\n \"regex\": \"bPod\",\n \"name\": \"BlackBerry OS\",\n \"version\": \"\"\n },\n {\n \"regex\": \"BeOS\",\n \"name\": \"BeOS\",\n \"version\": \"\"\n },\n {\n \"regex\": \"Symbian/3.+NokiaBrowser/7\\\\.3\",\n \"name\": \"Symbian^3\",\n \"version\": \"Anna\"\n },\n {\n \"regex\": \"Symbian/3.+NokiaBrowser/7\\\\.4\",\n \"name\": \"Symbian^3\",\n \"version\": \"Belle\"\n },\n {\n \"regex\": \"Symbian/3\",\n \"name\": \"Symbian^3\",\n \"version\": \"\"\n },\n {\n \"regex\": \"(?:Series ?60|SymbOS|S60)(?:[ /]?(\\\\d+[\\\\.\\\\d]+|V\\\\d+))?\",\n \"name\": \"Symbian OS Series 60\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Series40\",\n \"name\": \"Symbian OS Series 40\",\n \"version\": \"\"\n },\n {\n \"regex\": \"SymbianOS/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"Symbian OS\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"MeeGo|WeTab\",\n \"name\": \"MeeGo\",\n \"version\": \"\"\n },\n {\n \"regex\": \"Symbian(?: OS)?|SymbOS\",\n \"name\": \"Symbian OS\",\n \"version\": \"\"\n },\n {\n \"regex\": \"Nokia\",\n \"name\": \"Symbian\",\n \"version\": \"\"\n },\n {\n \"regex\": \"(?:Mobile|Tablet);.+Firefox/\\\\d+\\\\.\\\\d+\",\n \"name\": \"Firefox OS\",\n \"version\": \"\"\n },\n {\n \"regex\": \"RISC OS(?:-NC)?(?:[ /](\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"RISC OS\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Inferno(?:[ /](\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Inferno\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"bada(?:[ /](\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Bada\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"(?:Brew(?!-Applet)(?: MP)?|BMP)(?:[ /](\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Brew\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"GoogleTV(?:[ /](\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Google TV\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"AppleTV(?:/?(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Apple TV\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"WebTV/(\\\\d+[\\\\.\\\\d]+)\",\n \"name\": \"WebTV\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"RemixOS 5.1.1\",\n \"name\": \"Remix OS\",\n \"version\": \"1\"\n },\n {\n \"regex\": \"RemixOS 6.0\",\n \"name\": \"Remix OS\",\n \"version\": \"2\"\n },\n {\n \"regex\": \"RemixOS\",\n \"name\": \"Remix OS\",\n \"version\": \"\"\n },\n {\n \"regex\": \"(?:SunOS|Solaris)(?:[/ ](\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Solaris\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"AIX(?:[/ ]?(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"AIX\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"HP-UX(?:[/ ]?(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"HP-UX\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"FreeBSD(?:[/ ]?(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"FreeBSD\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"NetBSD(?:[/ ]?(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"NetBSD\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"OpenBSD(?:[/ ]?(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"OpenBSD\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"DragonFly(?:[/ ]?(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"DragonFly\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Syllable(?:[/ ]?(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"Syllable\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"IRIX(?:;64)?(?:[/ ]?(\\\\d+[\\\\.\\\\d]+))\",\n \"name\": \"IRIX\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"OSF1(?:[/ ]?v?(\\\\d+[\\\\.\\\\d]+))?\",\n \"name\": \"OSF1\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Nintendo (Wii|Switch)\",\n \"name\": \"Nintendo\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"PlayStation ?([34])\",\n \"name\": \"PlayStation\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"Xbox|KIN\\\\.(?:One|Two)\",\n \"name\": \"Xbox\",\n \"version\": \"360\"\n },\n {\n \"regex\": \"Nitro|Nintendo ([3]?DS[i]?)\",\n \"name\": \"Nintendo Mobile\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"PlayStation ((?:Portable|Vita))\",\n \"name\": \"PlayStation Portable\",\n \"version\": \"$1\"\n },\n {\n \"regex\": \"OS/2\",\n \"name\": \"OS/2\",\n \"version\": \"\"\n },\n {\n \"regex\": \"Linux(?:OS)?[^a-z]\",\n \"name\": \"GNU/Linux\",\n \"version\": \"\"\n }\n]\n", "{\n \"operatingSystem\": {\n \"AIX\": \"AIX\",\n \"AND\": \"Android\",\n \"AMG\": \"AmigaOS\",\n \"ATV\": \"Apple TV\",\n \"ARL\": \"Arch Linux\",\n \"BTR\": \"BackTrack\",\n \"SBA\": \"Bada\",\n \"BEO\": \"BeOS\",\n \"BLB\": \"BlackBerry OS\",\n \"QNX\": \"BlackBerry Tablet OS\",\n \"BMP\": \"Brew\",\n \"CES\": \"CentOS\",\n \"COS\": \"Chrome OS\",\n \"CYN\": \"CyanogenMod\",\n \"DEB\": \"Debian\",\n \"DEE\": \"Deepin\",\n \"DFB\": \"DragonFly\",\n \"FED\": \"Fedora\",\n \"FOS\": \"Firefox OS\",\n \"FIR\": \"Fire OS\",\n \"FRE\": \"Freebox\",\n \"BSD\": \"FreeBSD\",\n \"FYD\": \"FydeOS\",\n \"GNT\": \"Gentoo\",\n \"GTV\": \"Google TV\",\n \"HPX\": \"HP-UX\",\n \"HAI\": \"Haiku OS\",\n \"IRI\": \"IRIX\",\n \"INF\": \"Inferno\",\n \"KOS\": \"KaiOS\",\n \"KNO\": \"Knoppix\",\n \"KBT\": \"Kubuntu\",\n \"LIN\": \"GNU/Linux\",\n \"LBT\": \"Lubuntu\",\n \"VLN\": \"VectorLinux\",\n \"MAC\": \"Mac\",\n \"MAE\": \"Maemo\",\n \"MDR\": \"Mandriva\",\n \"SMG\": \"MeeGo\",\n \"MCD\": \"MocorDroid\",\n \"MIN\": \"Mint\",\n \"MLD\": \"MildWild\",\n \"MOR\": \"MorphOS\",\n \"NBS\": \"NetBSD\",\n \"MTK\": \"MTK / Nucleus\",\n \"MRE\": \"MRE\",\n \"WII\": \"Nintendo\",\n \"NDS\": \"Nintendo Mobile\",\n \"OS2\": \"OS/2\",\n \"T64\": \"OSF1\",\n \"OBS\": \"OpenBSD\",\n \"ORD\": \"Ordissimo\",\n \"PSP\": \"PlayStation Portable\",\n \"PS3\": \"PlayStation\",\n \"RHT\": \"Red Hat\",\n \"ROS\": \"RISC OS\",\n \"RSO\": \"Rosa\",\n \"REM\": \"Remix OS\",\n \"RZD\": \"RazoDroiD\",\n \"SAB\": \"Sabayon\",\n \"SSE\": \"SUSE\",\n \"SAF\": \"Sailfish OS\",\n \"SEE\": \"SeewoOS\",\n \"SLW\": \"Slackware\",\n \"SOS\": \"Solaris\",\n \"SYL\": \"Syllable\",\n \"SYM\": \"Symbian\",\n \"SYS\": \"Symbian OS\",\n \"S40\": \"Symbian OS Series 40\",\n \"S60\": \"Symbian OS Series 60\",\n \"SY3\": \"Symbian^3\",\n \"TDX\": \"ThreadX\",\n \"TIZ\": \"Tizen\",\n \"TOS\": \"TmaxOS\",\n \"UBT\": \"Ubuntu\",\n \"WAS\": \"watchOS\",\n \"WTV\": \"WebTV\",\n \"WHS\": \"Whale OS\",\n \"WIN\": \"Windows\",\n \"WCE\": \"Windows CE\",\n \"WIO\": \"Windows IoT\",\n \"WMO\": \"Windows Mobile\",\n \"WPH\": \"Windows Phone\",\n \"WRT\": \"Windows RT\",\n \"XBX\": \"Xbox\",\n \"XBT\": \"Xubuntu\",\n \"YNS\": \"YunOs\",\n \"IOS\": \"iOS\",\n \"POS\": \"palmOS\",\n \"WOS\": \"webOS\"\n },\n \"osFamilies\": {\n \"Android\": [\"AND\", \"CYN\", \"FIR\", \"REM\", \"RZD\", \"MLD\", \"MCD\", \"YNS\"],\n \"AmigaOS\": [\"AMG\", \"MOR\"],\n \"Apple TV\": [\"ATV\"],\n \"BlackBerry\": [\"BLB\", \"QNX\"],\n \"Brew\": [\"BMP\"],\n \"BeOS\": [\"BEO\", \"HAI\"],\n \"Chrome OS\": [\"COS\", \"FYD\", \"SEE\"],\n \"Firefox OS\": [\"FOS\", \"KOS\"],\n \"Gaming Console\": [\"WII\", \"PS3\"],\n \"Google TV\": [\"GTV\"],\n \"IBM\": [\"OS2\"],\n \"iOS\": [\"IOS\", \"WAS\"],\n \"RISC OS\": [\"ROS\"],\n \"GNU/Linux\": [\n \"LIN\", \"ARL\", \"DEB\", \"KNO\", \"MIN\", \"UBT\", \"KBT\", \"XBT\", \"LBT\", \"FED\",\n \"RHT\", \"VLN\", \"MDR\", \"GNT\", \"SAB\", \"SLW\", \"SSE\", \"CES\", \"BTR\", \"SAF\",\n \"ORD\", \"TOS\", \"RSO\", \"DEE\", \"FRE\"\n ],\n \"Mac\": [\"MAC\"],\n \"Mobile Gaming Console\": [\"PSP\", \"NDS\", \"XBX\"],\n \"Real-time OS\": [\"MTK\", \"TDX\", \"MRE\"],\n \"Other Mobile\": [\"WOS\", \"POS\", \"SBA\", \"TIZ\", \"SMG\", \"MAE\"],\n \"Symbian\": [\"SYM\", \"SYS\", \"SY3\", \"S60\", \"S40\"],\n \"Unix\": [\"SOS\", \"AIX\", \"HPX\", \"BSD\", \"NBS\", \"OBS\", \"DFB\", \"SYL\", \"IRI\", \"T64\", \"INF\"],\n \"WebTV\": [\"WTV\"],\n \"Windows\": [\"WIN\"],\n \"Windows Mobile\": [\"WPH\", \"WMO\", \"WCE\", \"WRT\", \"WIO\"],\n \"Other Smart TV\": [\"WHS\"]\n }\n}\n", "\"use strict\";\nvar __importDefault = (this && this.__importDefault) || function (mod) {\n return (mod && mod.__esModule) ? mod : { \"default\": mod };\n};\nObject.defineProperty(exports, \"__esModule\", { value: true });\nconst oss_json_1 = __importDefault(require(\"../../fixtures/regexes/oss.json\"));\nconst version_1 = require(\"../../utils/version\");\nconst variable_replacement_1 = require(\"../../utils/variable-replacement\");\nconst user_agent_1 = require(\"../../utils/user-agent\");\nconst operating_system_json_1 = __importDefault(require(\"./fixtures/operating-system.json\"));\nconst desktopOsArray = [\"AmigaOS\", \"IBM\", \"GNU/Linux\", \"Mac\", \"Unix\", \"Windows\", \"BeOS\", \"Chrome OS\"];\nconst shortOsNames = operating_system_json_1.default.operatingSystem;\nconst osFamilies = operating_system_json_1.default.osFamilies;\nclass OperatingSystemParser {\n constructor(options) {\n this.options = {\n versionTruncation: 1\n };\n this.parse = (userAgent) => {\n const result = {\n name: \"\",\n version: \"\",\n platform: this.parsePlatform(userAgent)\n };\n for (const operatingSystem of oss_json_1.default) {\n const match = user_agent_1.userAgentParser(operatingSystem.regex, userAgent);\n if (!match)\n continue;\n result.name = variable_replacement_1.variableReplacement(operatingSystem.name, match);\n result.version = version_1.formatVersion(variable_replacement_1.variableReplacement(operatingSystem.version, match), this.options.versionTruncation);\n if (result.name === \"lubuntu\") {\n result.name = \"Lubuntu\";\n }\n if (result.name === \"debian\") {\n result.name = \"Debian\";\n }\n if (result.name === \"YunOS\") {\n result.name = \"YunOs\";\n }\n return result;\n }\n return null;\n };\n this.parsePlatform = (userAgent) => {\n if (user_agent_1.userAgentParser(\"arm|aarch64|Watch ?OS|Watch1,[12]\", userAgent)) {\n return \"ARM\";\n }\n if (user_agent_1.userAgentParser(\"mips\", userAgent)) {\n return \"MIPS\";\n }\n if (user_agent_1.userAgentParser(\"sh4\", userAgent)) {\n return \"SuperH\";\n }\n if (user_agent_1.userAgentParser(\"WOW64|x64|win64|amd64|x86_?64\", userAgent)) {\n return \"x64\";\n }\n if (user_agent_1.userAgentParser(\"(?:i[0-9]|x)86|i86pc\", userAgent)) {\n return \"x86\";\n }\n return \"\";\n };\n this.options = Object.assign(Object.assign({}, this.options), options);\n }\n}\nexports.default = OperatingSystemParser;\nOperatingSystemParser.getDesktopOsArray = () => desktopOsArray;\nOperatingSystemParser.getOsFamily = (osName) => {\n const osShortName = OperatingSystemParser.getOsShortName(osName);\n for (const [osFamily, shortNames] of Object.entries(osFamilies)) {\n if (shortNames.includes(osShortName)) {\n return osFamily;\n }\n }\n return \"\";\n};\nOperatingSystemParser.getOsShortName = (osName) => {\n for (const [shortName, name] of Object.entries(shortOsNames)) {\n if (name === osName)\n return shortName;\n }\n return \"\";\n};\n", "{\n \"Dell\": [\n \"MDDR(JS)?\",\n \"MDDC(JS)?\",\n \"MDDS(JS)?\"\n ],\n \"Acer\": [\n \"MAAR(JS)?\"\n ],\n \"Sony\": [\n \"MASE(JS)?\",\n \"MASP(JS)?\",\n \"MASA(JS)?\"\n ],\n \"Asus\": [\n \"MAAU\",\n \"NP0[26789]\",\n \"ASJB\",\n \"ASU2(JS)?\"\n ],\n \"Samsung\": [\n \"MASM(JS)?\",\n \"SMJB\"\n ],\n \"Lenovo\": [\n \"MALC(JS)?\",\n \"MALE(JS)?\",\n \"MALN(JS)?\",\n \"LCJB\",\n \"LEN2\"\n ],\n \"Toshiba\": [\n \"MATM(JS)?\",\n \"MATB(JS)?\",\n \"MATP(JS)?\",\n \"TNJB\",\n \"TAJB\"\n ],\n \"Medion\": [\n \"MAMD\"\n ],\n \"MSI\": [\n \"MAMI(JS)?\",\n \"MAM3\"\n ],\n \"Gateway\": [\n \"MAGW(JS)?\"\n ],\n \"Fujitsu\": [\n \"MAFS(JS)?\",\n \"FSJB\"\n ],\n \"Compaq\": [\n \"CPDTDF\",\n \"CPNTDF(JS?)\",\n \"CMNTDF(JS)?\",\n \"CMDTDF(JS)?\"\n ],\n \"HP\": [\n \"HPCMHP\",\n \"HPNTDF(JS)?\",\n \"HPDTDF(JS)?\"\n ],\n \"Hyrican\": [\n \"MANM(JS)?\"\n ],\n \"Ordissimo\": [\n \"Ordissimo\",\n \"webissimo3\"\n ]\n}\n", "\"use strict\";\nvar __importDefault = (this && this.__importDefault) || function (mod) {\n return (mod && mod.__esModule) ? mod : { \"default\": mod };\n};\nObject.defineProperty(exports, \"__esModule\", { value: true });\nconst vendorfragments_json_1 = __importDefault(require(\"../../fixtures/regexes/vendorfragments.json\"));\nconst user_agent_1 = require(\"../../utils/user-agent\");\nclass VendorFragmentParser {\n constructor() {\n this.parse = (userAgent) => {\n for (const [brand, vendorFragment] of Object.entries(vendorfragments_json_1.default)) {\n for (const regex of vendorFragment) {\n const match = user_agent_1.userAgentParser(regex, userAgent);\n if (!match)\n continue;\n return brand;\n }\n }\n return \"\";\n };\n }\n}\nexports.default = VendorFragmentParser;\n", "[\n {\n \"regex\": \"360Spider(-Image|-Video)?\",\n \"name\": \"360Spider\",\n \"category\": \"Search bot\",\n \"url\": \"https://www.so.com/help/help_3_2.html\",\n \"producer\": {\n \"name\": \"Online Media Group, Inc.\",\n \"url\": \"\"\n }\n },\n {\n \"regex\": \"Aboundex\",\n \"name\": \"Aboundexbot\",\n \"category\": \"Search bot\",\n \"url\": \"http://www.aboundex.com/crawler/\",\n \"producer\": {\n \"name\": \"Aboundex.com\",\n \"url\": \"http://www.aboundex.com\"\n }\n },\n {\n \"regex\": \"AcoonBot\",\n \"name\": \"Acoon\",\n \"category\": \"Search bot\",\n \"url\": \"http://www.acoon.de/robot.asp\",\n \"producer\": {\n \"name\": \"Acoon GmbH\",\n \"url\": \"http://www.acoon.de\"\n }\n },\n {\n \"regex\": \"AddThis\\\\.com\",\n \"name\": \"AddThis.com\",\n \"category\": \"Social Media Agent\",\n \"url\": \"\",\n \"producer\": {\n \"name\": \"Clearspring Technologies, Inc.\",\n \"url\": \"http://www.clearspring.com\"\n }\n },\n {\n \"regex\": \"AhrefsBot\",\n \"name\": \"aHrefs Bot\",\n \"category\": \"Crawler\",\n \"url\": \"https://ahrefs.com/robot\",\n \"producer\": {\n \"name\": \"Ahrefs Pte Ltd\",\n \"url\": \"https://ahrefs.com/robot\"\n }\n },\n {\n \"regex\": \"ia_archiver|alexabot|verifybot\",\n \"name\": \"Alexa Crawler\",\n \"category\": \"Search bot\",\n \"url\": \"https://support.alexa.com/hc/en-us/sections/200100794-Crawlers\",\n \"producer\": {\n \"name\": \"Alexa Internet\",\n \"url\": \"https://www.alexa.com\"\n }\n },\n {\n \"regex\": \"alexa site audit\",\n \"name\": \"Alexa Site Audit\",\n \"category\": \"Site Monitor\",\n \"url\": \"https://support.alexa.com/hc/en-us/articles/200450194\",\n \"producer\": {\n \"name\": \"Alexa Internet\",\n \"url\": \"https://www.alexa.com\"\n }\n },\n {\n \"regex\": \"Amazon[ -]Route ?53[ -]Health[ -]Check[ -]Service\",\n \"name\": \"Amazon Route53 Health Check\",\n \"category\": \"Service Agent\",\n \"producer\": {\n \"name\": \"Amazon Web Services\",\n \"url\": \"https://aws.amazon.com/\"\n }\n },\n {\n \"regex\": \"AmorankSpider\",\n \"name\": \"Amorank Spider\",\n \"category\": \"Crawler\",\n \"url\": \"http://amorank.com/webcrawler.html\",\n \"producer\": {\n \"name\": \"Amorank\",\n \"url\": \"http://www.amorank.com\"\n }\n },\n {\n \"regex\": \"ApacheBench\",\n \"name\": \"ApacheBench\",\n \"category\": \"Benchmark\",\n \"url\": \"https://httpd.apache.org/docs/2.4/programs/ab.html\",\n \"producer\": {\n \"name\": \"The Apache Software Foundation\",\n \"url\": \"https://www.apache.org/foundation/\"\n }\n },\n {\n \"regex\": \"Applebot\",\n \"name\": \"Applebot\",\n \"category\": \"Crawler\",\n \"url\": \"https://support.apple.com/en-us/HT204683\",\n \"producer\": {\n \"name\": \"Apple Inc\",\n \"url\": \"https://www.apple.com\"\n }\n },\n {\n \"regex\": \"AppSignalBot\",\n \"name\": \"AppSignalBot\",\n \"category\": \"Site Monitor\",\n \"url\": \"https://docs.appsignal.com/uptime-monitoring/\",\n \"producer\": {\n \"name\": \"AppSignal\",\n \"url\": \"https://appsignal.com/\"\n }\n },\n {\n \"regex\": \"Arachni\",\n \"name\": \"Arachni\",\n \"category\": \"Security Checker\",\n \"url\": \"https://www.arachni-scanner.com/\",\n \"producer\": {\n \"name\": \"Sarosys LLC\",\n \"url\": \"https://www.sarosys.com/\"\n }\n },\n {\n \"regex\": \"AspiegelBot\",\n \"name\": \"AspiegelBot\",\n \"category\": \"Crawler\",\n \"url\": \"https://aspiegel.com/\",\n \"producer\": {\n \"name\": \"Huawei\",\n \"url\": \"https://www.huawei.com/\"\n }\n },\n {\n \"regex\": \"Castro 2, Episode Duration Lookup\",\n \"name\": \"Castro 2\",\n \"category\": \"Service Agent\",\n \"url\": \"http://supertop.co/castro/\",\n \"producer\": {\n \"name\": \"Supertop\",\n \"url\": \"http://supertop.co\"\n }\n },\n {\n \"regex\": \"Curious George\",\n \"name\": \"Analytics SEO Crawler\",\n \"category\": \"Crawler\",\n \"url\": \"http://www.analyticsseo.com/crawler\",\n \"producer\": {\n \"name\": \"Analytics SEO\",\n \"url\": \"http://www.analyticsseo.com\"\n }\n },\n {\n \"regex\": \"archive\\\\.org_bot|special_archiver\",\n \"name\": \"archive.org bot\",\n \"category\": \"Crawler\",\n \"url\": \"https://archive.org/details/archive.org_bot\",\n \"producer\": {\n \"name\": \"The Internet Archive\",\n \"url\": \"https://archive.org\"\n }\n },\n {\n \"regex\": \"Ask Jeeves/Teoma\",\n \"name\": \"Ask Jeeves\",\n \"category\": \"Search bot\",\n \"url\": \"\",\n \"producer\": {\n \"name\": \"Ask Jeeves Inc.\",\n \"url\": \"http://www.ask.com\"\n }\n },\n {\n \"regex\": \"Backlink-Check\\\\.de\",\n \"name\": \"Backlink-Check.de\",\n \"category\": \"Crawler\",\n \"url\": \"http://www.backlink-check.de/bot.html\",\n \"producer\": {\n \"name\": \"Mediagreen Medienservice\",\n \"url\": \"http://www.backlink-check.de\"\n }\n },\n {\n \"regex\": \"BacklinkCrawler\",\n \"name\": \"BacklinkCrawler\",\n \"category\": \"Crawler\",\n \"url\": \"http://www.backlinktest.com/crawler.html\",\n \"producer\": {\n \"name\": \"2.0Promotion GbR\",\n \"url\": \"http://www.backlinktest.com\"\n }\n },\n {\n \"regex\": \"baiduspider(-image)?|baidu Transcoder|baidu.*spider\",\n \"name\": \"Baidu Spider\",\n \"category\": \"Search bot\",\n \"url\": \"http://www.baidu.com/search/spider.htm\",\n \"producer\": {\n \"name\": \"Baidu\",\n \"url\": \"http://www.baidu.com\"\n }\n },\n {\n \"regex\": \"BazQux\",\n \"name\": \"BazQux Reader\",\n \"url\": \"https://bazqux.com/fetcher\",\n \"category\": \"Feed Fetcher\",\n \"producer\": {\n \"name\": \"\",\n \"url\": \"\"\n }\n },\n {\n \"regex\": \"MSNBot|msrbot|bingbot|BingPreview|msnbot-(UDiscovery|NewsBlogs)|adidxbot\",\n \"name\": \"BingBot\",\n \"category\": \"Search bot\",\n \"url\": \"http://search.msn.com/msnbot.htmn\",\n \"producer\": {\n \"name\": \"Microsoft Corporation\",\n \"url\": \"http://www.microsoft.com\"\n }\n },\n {\n \"regex\": \"Blekkobot\",\n \"name\": \"Blekkobot\",\n \"category\": \"Search bot\",\n \"url\": \"http://blekko.com/about/blekkobot\",\n \"producer\": {\n \"name\": \"Blekko\",\n \"url\": \"http://blekko.com\"\n }\n },\n {\n \"regex\": \"BLEXBot(Test)?\",\n \"name\": \"BLEXBot Crawler\",\n \"category\": \"Crawler\",\n \"url\": \"http://webmeup-crawler.com\",\n \"producer\": {\n \"name\": \"WebMeUp\",\n \"url\": \"http://webmeup.com\"\n }\n },\n {\n \"regex\": \"Bloglovin\",\n \"name\": \"Bloglovin\",\n \"url\": \"http://www.bloglovin.com\",\n \"category\": \"Feed Fetcher\",\n \"producer\": {\n \"name\": \"\",\n \"url\": \"\"\n }\n },\n {\n \"regex\": \"Blogtrottr\",\n \"name\": \"Blogtrottr\",\n \"url\": \"\",\n \"category\": \"Feed Fetcher\",\n \"producer\": {\n \"name\": \"Blogtrottr Ltd\",\n \"url\": \"https://blogtrottr.com/\"\n }\n },\n {\n \"regex\": \"BoardReader Blog Indexer\",\n \"name\": \"BoardReader Blog Indexer\",\n \"category\": \"Crawler\",\n \"producer\": {\n \"name\": \"BoardReader\",\n \"url\": \"https://boardreader.com/\"\n }\n },\n {\n \"regex\": \"BountiiBot\",\n \"name\": \"Bountii Bot\",\n \"category\": \"Search bot\",\n \"url\": \"http://bountii.com/contact.php\",\n \"producer\": {\n \"name\": \"Bountii Inc.\",\n \"url\": \"http://bountii.com\"\n }\n },\n {\n \"regex\": \"Browsershots\",\n \"name\": \"Browsershots\",\n \"category\": \"Service Agent\",\n \"url\": \"http://browsershots.org/faq\",\n \"producer\": {\n \"name\": \"Browsershots.org\",\n \"url\": \"http://browsershots.org\"\n }\n },\n {\n \"regex\": \"BUbiNG\",\n \"name\": \"BUbiNG\",\n \"category\": \"Crawler\",\n \"url\": \"http://law.di.unimi.it/BUbiNG.html\",\n \"producer\": {\n \"name\": \"The Laboratory for Web Algorithmics (LAW)\",\n \"url\": \"http://law.di.unimi.it/software.php#buging\"\n }\n },\n {\n \"regex\": \"(? {\n var _a, _b, _c, _d;\n for (const bot of bots_json_1.default) {\n const match = user_agent_1.userAgentParser(bot.regex, userAgent);\n if (!match)\n continue;\n return {\n name: bot.name,\n category: bot.category || \"\",\n url: bot.url || \"\",\n producer: {\n name: ((_b = (_a = bot) === null || _a === void 0 ? void 0 : _a.producer) === null || _b === void 0 ? void 0 : _b.name) || \"\",\n url: ((_d = (_c = bot) === null || _c === void 0 ? void 0 : _c.producer) === null || _d === void 0 ? void 0 : _d.url) || \"\"\n }\n };\n }\n return null;\n };\n }\n}\nmodule.exports = BotParser;\n", "\"use strict\";\nObject.defineProperty(exports, \"__esModule\", { value: true });\nexports.versionCompare = (v1, v2, operator) => {\n // discuss at: http://locutus.io/php/version_compare/\n // original by: Philippe Jausions (http://pear.php.net/user/jausions)\n // original by: Aidan Lister (http://aidanlister.com/)\n // reimplemented by: Kankrelune (http://www.webfaktory.info/)\n // improved by: Brett Zamir (http://brett-zamir.me)\n // improved by: Scott Baker\n // improved by: Theriault (https://github.com/Theriault)\n // example 1: version_compare('8.2.5rc', '8.2.5a')\n // returns 1: 1\n // example 2: version_compare('8.2.50', '8.2.52', '<')\n // returns 2: true\n // example 3: version_compare('5.3.0-dev', '5.3.0')\n // returns 3: -1\n // example 4: version_compare('4.1.0.52','4.01.0.51')\n // returns 4: 1\n // Important: compare must be initialized at 0.\n let i;\n let x;\n let compare = 0;\n // vm maps textual PHP versions to negatives so they're less than 0.\n // PHP currently defines these as CASE-SENSITIVE. It is important to\n // leave these as negatives so that they can come before numerical versions\n // and as if no letters were there to begin with.\n // (1alpha is < 1 and < 1.1 but > 1dev1)\n // If a non-numerical value can't be mapped to this table, it receives\n // -7 as its value.\n const vm = {\n \"dev\": -6,\n \"alpha\": -5,\n \"a\": -5,\n \"beta\": -4,\n \"b\": -4,\n \"RC\": -3,\n \"rc\": -3,\n \"#\": -2,\n \"p\": 1,\n \"pl\": 1\n };\n // This function will be called to prepare each version argument.\n // It replaces every _, -, and + with a dot.\n // It surrounds any nonsequence of numbers/dots with dots.\n // It replaces sequences of dots with a single dot.\n // version_compare('4..0', '4.0') === 0\n // Important: A string of 0 length needs to be converted into a value\n // even less than an unexisting value in vm (-7), hence [-8].\n // It's also important to not strip spaces because of this.\n // version_compare('', ' ') === 1\n const prepVersion = (v) => {\n v = (\"\" + v).replace(/[_\\-+]/g, \".\");\n v = v.replace(/([^.\\d]+)/g, \".$1.\").replace(/\\.{2,}/g, \".\");\n return (!v.length ? [-8] : v.split(\".\"));\n };\n // This converts a version component to a number.\n // Empty component becomes 0.\n // Non-numerical component becomes a negative number.\n // Numerical component becomes itself as an integer.\n const numVersion = (v) => {\n return !v ? 0 : (isNaN(v) ? vm[v] || -7 : parseInt(v, 10));\n };\n v1 = prepVersion(v1);\n v2 = prepVersion(v2);\n x = Math.max(v1.length, v2.length);\n for (i = 0; i < x; i++) {\n if (v1[i] === v2[i]) {\n continue;\n }\n v1[i] = numVersion(v1[i]);\n v2[i] = numVersion(v2[i]);\n if (v1[i] < v2[i]) {\n compare = -1;\n break;\n }\n else if (v1[i] > v2[i]) {\n compare = 1;\n break;\n }\n }\n if (!operator) {\n return compare;\n }\n // Important: operator is CASE-SENSITIVE.\n // \"No operator\" seems to be treated as \"<.\"\n // Any other values seem to make the function return null.\n switch (operator) {\n case \">\":\n case \"gt\":\n return (compare > 0);\n case \">=\":\n case \"ge\":\n return (compare >= 0);\n case \"<=\":\n case \"le\":\n return (compare <= 0);\n case \"===\":\n case \"=\":\n case \"eq\":\n return (compare === 0);\n case \"<>\":\n case \"!==\":\n case \"ne\":\n return (compare !== 0);\n case \"\":\n case \"<\":\n case \"lt\":\n return (compare < 0);\n default:\n return null;\n }\n};\n", "\"use strict\";\nvar __importDefault = (this && this.__importDefault) || function (mod) {\n return (mod && mod.__esModule) ? mod : { \"default\": mod };\n};\nconst client_1 = __importDefault(require(\"./parsers/client\"));\nconst device_1 = __importDefault(require(\"./parsers/device\"));\nconst operating_system_1 = __importDefault(require(\"./parsers/operating-system\"));\nconst vendor_fragment_1 = __importDefault(require(\"./parsers/vendor-fragment\"));\nconst browser_1 = __importDefault(require(\"./parsers/client/browser\"));\nconst BotParser = require(\"./parsers/bot\");\nconst user_agent_1 = require(\"./utils/user-agent\");\nconst version_compare_1 = require(\"./utils/version-compare\");\nclass DeviceDetector {\n constructor(options) {\n // Default options\n this.options = {\n skipBotDetection: false,\n versionTruncation: 1\n };\n this.parse = (userAgent) => {\n var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p;\n const result = {\n client: this.clientParser.parse(userAgent),\n os: this.operatingSystemParser.parse(userAgent),\n device: this.deviceParser.parse(userAgent),\n bot: this.options.skipBotDetection ? null : this.botParser.parse(userAgent)\n };\n const osName = (_a = result.os) === null || _a === void 0 ? void 0 : _a.name;\n const osVersion = (_b = result.os) === null || _b === void 0 ? void 0 : _b.version;\n const osFamily = operating_system_1.default.getOsFamily(osName || \"\");\n if (!((_c = result.device) === null || _c === void 0 ? void 0 : _c.brand)) {\n const brand = this.vendorFragmentParser.parse(userAgent);\n if (brand) {\n if (!result.device) {\n result.device = this.createDeviceObject();\n }\n result.device.brand = brand;\n }\n }\n /**\n * Assume all devices running iOS / Mac OS are from Apple\n */\n if (!((_d = result.device) === null || _d === void 0 ? void 0 : _d.brand) && [\"Apple TV\", \"watchOS\", \"iOS\", \"Mac\"].includes(osName || \"\")) {\n if (!result.device) {\n result.device = this.createDeviceObject();\n }\n result.device.brand = \"Apple\";\n }\n /**\n * Chrome on Android passes the device type based on the keyword 'Mobile'\n * If it is present the device should be a smartphone, otherwise it's a tablet\n * See https://developer.chrome.com/multidevice/user-agent#chrome_for_android_user_agent\n * Note: We do not check for browser (family) here, as there might be mobile apps using Chrome, that won't have\n * a detected browser, but can still be detected. So we check the useragent for Chrome instead.\n */\n if (!((_e = result.device) === null || _e === void 0 ? void 0 : _e.type) && osFamily === \"Android\" && user_agent_1.userAgentParser(\"Chrome/[\\\\.0-9]*\", userAgent)) {\n if (user_agent_1.userAgentParser(\"Chrome/[.0-9]* (?:Mobile|eliboM)\", userAgent)) {\n if (!result.device) {\n result.device = this.createDeviceObject();\n }\n result.device.type = \"smartphone\";\n }\n else if (user_agent_1.userAgentParser(\"Chrome/[.0-9]* (?!Mobile)\", userAgent)) {\n if (!result.device) {\n result.device = this.createDeviceObject();\n }\n result.device.type = \"tablet\";\n }\n }\n /**\n * Some user agents simply contain the fragment 'Android; Tablet;' or 'Opera Tablet', so we assume those devices are tablets\n */\n if (!((_f = result.device) === null || _f === void 0 ? void 0 : _f.type) && this.hasAndroidTabletFragment(userAgent) || user_agent_1.userAgentParser(\"Opera Tablet\", userAgent)) {\n if (!result.device) {\n result.device = this.createDeviceObject();\n }\n result.device.type = \"tablet\";\n }\n /**\n * Some user agents simply contain the fragment 'Android; Mobile;', so we assume those devices are smartphones\n */\n if (!((_g = result.device) === null || _g === void 0 ? void 0 : _g.type) && this.hasAndroidMobileFragment(userAgent)) {\n if (!result.device) {\n result.device = this.createDeviceObject();\n }\n result.device.type = \"smartphone\";\n }\n /**\n * Android up to 3.0 was designed for smartphones only. But as 3.0, which was tablet only, was published\n * too late, there were a bunch of tablets running with 2.x\n * With 4.0 the two trees were merged and it is for smartphones and tablets\n *\n * So were are expecting that all devices running Android < 2 are smartphones\n * Devices running Android 3.X are tablets. Device type of Android 2.X and 4.X+ are unknown\n */\n if (!((_h = result.device) === null || _h === void 0 ? void 0 : _h.type) && osName === \"Android\" && osVersion !== \"\") {\n if (version_compare_1.versionCompare(osVersion, \"2.0\") === -1) {\n if (!result.device) {\n result.device = this.createDeviceObject();\n }\n result.device.type = \"smartphone\";\n }\n else if (version_compare_1.versionCompare(osVersion, \"3.0\") >= 0 && version_compare_1.versionCompare(osVersion, \"4.0\") === -1) {\n if (!result.device) {\n result.device = this.createDeviceObject();\n }\n result.device.type = \"tablet\";\n }\n }\n /**\n * All detected feature phones running android are more likely smartphones\n */\n if (((_j = result.device) === null || _j === void 0 ? void 0 : _j.type) === \"feature phone\" && osFamily === \"Android\") {\n result.device.type = \"smartphone\";\n }\n /**\n * According to http://msdn.microsoft.com/en-us/library/ie/hh920767(v=vs.85).aspx\n * Internet Explorer 10 introduces the \"Touch\" UA string token. If this token is present at the end of the\n * UA string, the computer has touch capability, and is running Windows 8 (or later).\n * This UA string will be transmitted on a touch-enabled system running Windows 8 (RT)\n *\n * As most touch enabled devices are tablets and only a smaller part are desktops/notebooks we assume that\n * all Windows 8 touch devices are tablets.\n */\n if (!((_k = result.device) === null || _k === void 0 ? void 0 : _k.type)\n && this.isToucheEnabled(userAgent)\n && (osName === \"Windows RT\"\n || (osName === \"Windows\"\n && version_compare_1.versionCompare(osVersion, \"8.0\") >= 0))) {\n if (!result.device) {\n result.device = this.createDeviceObject();\n }\n result.device.type = \"tablet\";\n }\n /**\n * All devices running Opera TV Store are assumed to be televisions\n */\n if (user_agent_1.userAgentParser(\"Opera TV Store\", userAgent)) {\n if (!result.device) {\n result.device = this.createDeviceObject();\n }\n result.device.type = \"television\";\n }\n /**\n * All devices running Tizen TV or SmartTV are assumed to be televisions\n */\n if (user_agent_1.userAgentParser(\"SmartTV|Tizen.+ TV .+$\", userAgent)) {\n if (!result.device) {\n result.device = this.createDeviceObject();\n }\n result.device.type = \"television\";\n }\n /**\n * Devices running Kylo or Espital TV Browsers are assumed to be televisions\n */\n if (!((_l = result.device) === null || _l === void 0 ? void 0 : _l.type) && [\"Kylo\", \"Espial TV Browser\"].includes(((_m = result.client) === null || _m === void 0 ? void 0 : _m.name) || \"\")) {\n if (!result.device) {\n result.device = this.createDeviceObject();\n }\n result.device.type = \"television\";\n }\n /**\n * Set device type to desktop if string ua contains desktop\n */\n const hasDesktop = \"desktop\" !== ((_o = result.device) === null || _o === void 0 ? void 0 : _o.type)\n && null !== user_agent_1.userAgentParser(\"Desktop\", userAgent)\n && this.hasDesktopFragment(userAgent);\n if (hasDesktop) {\n if (!result.device) {\n result.device = this.createDeviceObject();\n }\n result.device.type = \"desktop\";\n }\n // set device type to desktop for all devices running a desktop os that were not detected as an other device type\n if (!((_p = result.device) === null || _p === void 0 ? void 0 : _p.type) && this.isDesktop(result, osFamily)) {\n if (!result.device) {\n result.device = this.createDeviceObject();\n }\n result.device.type = \"desktop\";\n }\n return result;\n };\n this.hasAndroidMobileFragment = (userAgent) => {\n return user_agent_1.userAgentParser(\"Android( [\\.0-9]+)?; Mobile;\", userAgent);\n };\n this.hasAndroidTabletFragment = (userAgent) => {\n return user_agent_1.userAgentParser(\"Android( [\\.0-9]+)?; Tablet;\", userAgent);\n };\n this.hasDesktopFragment = (userAgent) => {\n return user_agent_1.userAgentParser(\"Desktop (x(?:32|64)|WOW64);\", userAgent);\n };\n this.isDesktop = (result, osFamily) => {\n if (!result.os) {\n return false;\n }\n // Check for browsers available for mobile devices only\n if (this.usesMobileBrowser(result.client)) {\n return false;\n }\n return operating_system_1.default.getDesktopOsArray().includes(osFamily);\n };\n this.usesMobileBrowser = (client) => {\n var _a, _b;\n if (!client)\n return false;\n return ((_a = client) === null || _a === void 0 ? void 0 : _a.type) === \"browser\" && browser_1.default.isMobileOnlyBrowser((_b = client) === null || _b === void 0 ? void 0 : _b.name);\n };\n this.isToucheEnabled = (userAgent) => {\n return user_agent_1.userAgentParser(\"Touch\", userAgent);\n };\n this.createDeviceObject = () => ({\n type: \"\",\n brand: \"\",\n model: \"\"\n });\n this.options = Object.assign(Object.assign({}, this.options), options);\n this.clientParser = new client_1.default(this.options);\n this.deviceParser = new device_1.default();\n this.operatingSystemParser = new operating_system_1.default(this.options);\n this.vendorFragmentParser = new vendor_fragment_1.default();\n this.botParser = new BotParser();\n }\n}\nmodule.exports = DeviceDetector;\n", "(function(root, factory) {\n 'use strict';\n // Universal Module Definition (UMD) to support AMD, CommonJS/Node.js, Rhino, and browsers.\n\n /* istanbul ignore next */\n if (typeof define === 'function' && define.amd) {\n define('stackframe', [], factory);\n } else if (typeof exports === 'object') {\n module.exports = factory();\n } else {\n root.StackFrame = factory();\n }\n}(this, function() {\n 'use strict';\n function _isNumber(n) {\n return !isNaN(parseFloat(n)) && isFinite(n);\n }\n\n function _capitalize(str) {\n return str.charAt(0).toUpperCase() + str.substring(1);\n }\n\n function _getter(p) {\n return function() {\n return this[p];\n };\n }\n\n var booleanProps = ['isConstructor', 'isEval', 'isNative', 'isToplevel'];\n var numericProps = ['columnNumber', 'lineNumber'];\n var stringProps = ['fileName', 'functionName', 'source'];\n var arrayProps = ['args'];\n var objectProps = ['evalOrigin'];\n\n var props = booleanProps.concat(numericProps, stringProps, arrayProps, objectProps);\n\n function StackFrame(obj) {\n if (!obj) return;\n for (var i = 0; i < props.length; i++) {\n if (obj[props[i]] !== undefined) {\n this['set' + _capitalize(props[i])](obj[props[i]]);\n }\n }\n }\n\n StackFrame.prototype = {\n getArgs: function() {\n return this.args;\n },\n setArgs: function(v) {\n if (Object.prototype.toString.call(v) !== '[object Array]') {\n throw new TypeError('Args must be an Array');\n }\n this.args = v;\n },\n\n getEvalOrigin: function() {\n return this.evalOrigin;\n },\n setEvalOrigin: function(v) {\n if (v instanceof StackFrame) {\n this.evalOrigin = v;\n } else if (v instanceof Object) {\n this.evalOrigin = new StackFrame(v);\n } else {\n throw new TypeError('Eval Origin must be an Object or StackFrame');\n }\n },\n\n toString: function() {\n var fileName = this.getFileName() || '';\n var lineNumber = this.getLineNumber() || '';\n var columnNumber = this.getColumnNumber() || '';\n var functionName = this.getFunctionName() || '';\n if (this.getIsEval()) {\n if (fileName) {\n return '[eval] (' + fileName + ':' + lineNumber + ':' + columnNumber + ')';\n }\n return '[eval]:' + lineNumber + ':' + columnNumber;\n }\n if (functionName) {\n return functionName + ' (' + fileName + ':' + lineNumber + ':' + columnNumber + ')';\n }\n return fileName + ':' + lineNumber + ':' + columnNumber;\n }\n };\n\n StackFrame.fromString = function StackFrame$$fromString(str) {\n var argsStartIndex = str.indexOf('(');\n var argsEndIndex = str.lastIndexOf(')');\n\n var functionName = str.substring(0, argsStartIndex);\n var args = str.substring(argsStartIndex + 1, argsEndIndex).split(',');\n var locationString = str.substring(argsEndIndex + 1);\n\n if (locationString.indexOf('@') === 0) {\n var parts = /@(.+?)(?::(\\d+))?(?::(\\d+))?$/.exec(locationString, '');\n var fileName = parts[1];\n var lineNumber = parts[2];\n var columnNumber = parts[3];\n }\n\n return new StackFrame({\n functionName: functionName,\n args: args || undefined,\n fileName: fileName,\n lineNumber: lineNumber || undefined,\n columnNumber: columnNumber || undefined\n });\n };\n\n for (var i = 0; i < booleanProps.length; i++) {\n StackFrame.prototype['get' + _capitalize(booleanProps[i])] = _getter(booleanProps[i]);\n StackFrame.prototype['set' + _capitalize(booleanProps[i])] = (function(p) {\n return function(v) {\n this[p] = Boolean(v);\n };\n })(booleanProps[i]);\n }\n\n for (var j = 0; j < numericProps.length; j++) {\n StackFrame.prototype['get' + _capitalize(numericProps[j])] = _getter(numericProps[j]);\n StackFrame.prototype['set' + _capitalize(numericProps[j])] = (function(p) {\n return function(v) {\n if (!_isNumber(v)) {\n throw new TypeError(p + ' must be a Number');\n }\n this[p] = Number(v);\n };\n })(numericProps[j]);\n }\n\n for (var k = 0; k < stringProps.length; k++) {\n StackFrame.prototype['get' + _capitalize(stringProps[k])] = _getter(stringProps[k]);\n StackFrame.prototype['set' + _capitalize(stringProps[k])] = (function(p) {\n return function(v) {\n this[p] = String(v);\n };\n })(stringProps[k]);\n }\n\n return StackFrame;\n}));\n", "(function(root, factory) {\n 'use strict';\n // Universal Module Definition (UMD) to support AMD, CommonJS/Node.js, Rhino, and browsers.\n\n /* istanbul ignore next */\n if (typeof define === 'function' && define.amd) {\n define('error-stack-parser', ['stackframe'], factory);\n } else if (typeof exports === 'object') {\n module.exports = factory(require('stackframe'));\n } else {\n root.ErrorStackParser = factory(root.StackFrame);\n }\n}(this, function ErrorStackParser(StackFrame) {\n 'use strict';\n\n var FIREFOX_SAFARI_STACK_REGEXP = /(^|@)\\S+:\\d+/;\n var CHROME_IE_STACK_REGEXP = /^\\s*at .*(\\S+:\\d+|\\(native\\))/m;\n var SAFARI_NATIVE_CODE_REGEXP = /^(eval@)?(\\[native code])?$/;\n\n return {\n /**\n * Given an Error object, extract the most information from it.\n *\n * @param {Error} error object\n * @return {Array} of StackFrames\n */\n parse: function ErrorStackParser$$parse(error) {\n if (typeof error.stacktrace !== 'undefined' || typeof error['opera#sourceloc'] !== 'undefined') {\n return this.parseOpera(error);\n } else if (error.stack && error.stack.match(CHROME_IE_STACK_REGEXP)) {\n return this.parseV8OrIE(error);\n } else if (error.stack) {\n return this.parseFFOrSafari(error);\n } else {\n throw new Error('Cannot parse given Error object');\n }\n },\n\n // Separate line and column numbers from a string of the form: (URI:Line:Column)\n extractLocation: function ErrorStackParser$$extractLocation(urlLike) {\n // Fail-fast but return locations like \"(native)\"\n if (urlLike.indexOf(':') === -1) {\n return [urlLike];\n }\n\n var regExp = /(.+?)(?::(\\d+))?(?::(\\d+))?$/;\n var parts = regExp.exec(urlLike.replace(/[()]/g, ''));\n return [parts[1], parts[2] || undefined, parts[3] || undefined];\n },\n\n parseV8OrIE: function ErrorStackParser$$parseV8OrIE(error) {\n var filtered = error.stack.split('\\n').filter(function(line) {\n return !!line.match(CHROME_IE_STACK_REGEXP);\n }, this);\n\n return filtered.map(function(line) {\n if (line.indexOf('(eval ') > -1) {\n // Throw away eval information until we implement stacktrace.js/stackframe#8\n line = line.replace(/eval code/g, 'eval').replace(/(\\(eval at [^()]*)|(,.*$)/g, '');\n }\n var sanitizedLine = line.replace(/^\\s+/, '').replace(/\\(eval code/g, '(').replace(/^.*?\\s+/, '');\n\n // capture and preseve the parenthesized location \"(/foo/my bar.js:12:87)\" in\n // case it has spaces in it, as the string is split on \\s+ later on\n var location = sanitizedLine.match(/ (\\(.+\\)$)/);\n\n // remove the parenthesized location from the line, if it was matched\n sanitizedLine = location ? sanitizedLine.replace(location[0], '') : sanitizedLine;\n\n // if a location was matched, pass it to extractLocation() otherwise pass all sanitizedLine\n // because this line doesn't have function name\n var locationParts = this.extractLocation(location ? location[1] : sanitizedLine);\n var functionName = location && sanitizedLine || undefined;\n var fileName = ['eval', ''].indexOf(locationParts[0]) > -1 ? undefined : locationParts[0];\n\n return new StackFrame({\n functionName: functionName,\n fileName: fileName,\n lineNumber: locationParts[1],\n columnNumber: locationParts[2],\n source: line\n });\n }, this);\n },\n\n parseFFOrSafari: function ErrorStackParser$$parseFFOrSafari(error) {\n var filtered = error.stack.split('\\n').filter(function(line) {\n return !line.match(SAFARI_NATIVE_CODE_REGEXP);\n }, this);\n\n return filtered.map(function(line) {\n // Throw away eval information until we implement stacktrace.js/stackframe#8\n if (line.indexOf(' > eval') > -1) {\n line = line.replace(/ line (\\d+)(?: > eval line \\d+)* > eval:\\d+:\\d+/g, ':$1');\n }\n\n if (line.indexOf('@') === -1 && line.indexOf(':') === -1) {\n // Safari eval frames only have function names and nothing else\n return new StackFrame({\n functionName: line\n });\n } else {\n var functionNameRegex = /((.*\".+\"[^@]*)?[^@]*)(?:@)/;\n var matches = line.match(functionNameRegex);\n var functionName = matches && matches[1] ? matches[1] : undefined;\n var locationParts = this.extractLocation(line.replace(functionNameRegex, ''));\n\n return new StackFrame({\n functionName: functionName,\n fileName: locationParts[0],\n lineNumber: locationParts[1],\n columnNumber: locationParts[2],\n source: line\n });\n }\n }, this);\n },\n\n parseOpera: function ErrorStackParser$$parseOpera(e) {\n if (!e.stacktrace || (e.message.indexOf('\\n') > -1 &&\n e.message.split('\\n').length > e.stacktrace.split('\\n').length)) {\n return this.parseOpera9(e);\n } else if (!e.stack) {\n return this.parseOpera10(e);\n } else {\n return this.parseOpera11(e);\n }\n },\n\n parseOpera9: function ErrorStackParser$$parseOpera9(e) {\n var lineRE = /Line (\\d+).*script (?:in )?(\\S+)/i;\n var lines = e.message.split('\\n');\n var result = [];\n\n for (var i = 2, len = lines.length; i < len; i += 2) {\n var match = lineRE.exec(lines[i]);\n if (match) {\n result.push(new StackFrame({\n fileName: match[2],\n lineNumber: match[1],\n source: lines[i]\n }));\n }\n }\n\n return result;\n },\n\n parseOpera10: function ErrorStackParser$$parseOpera10(e) {\n var lineRE = /Line (\\d+).*script (?:in )?(\\S+)(?:: In function (\\S+))?$/i;\n var lines = e.stacktrace.split('\\n');\n var result = [];\n\n for (var i = 0, len = lines.length; i < len; i += 2) {\n var match = lineRE.exec(lines[i]);\n if (match) {\n result.push(\n new StackFrame({\n functionName: match[3] || undefined,\n fileName: match[2],\n lineNumber: match[1],\n source: lines[i]\n })\n );\n }\n }\n\n return result;\n },\n\n // Opera 10.65+ Error.stack very similar to FF/Safari\n parseOpera11: function ErrorStackParser$$parseOpera11(error) {\n var filtered = error.stack.split('\\n').filter(function(line) {\n return !!line.match(FIREFOX_SAFARI_STACK_REGEXP) && !line.match(/^Error created at/);\n }, this);\n\n return filtered.map(function(line) {\n var tokens = line.split('@');\n var locationParts = this.extractLocation(tokens.pop());\n var functionCall = (tokens.shift() || '');\n var functionName = functionCall\n .replace(//, '$2')\n .replace(/\\([^)]*\\)/g, '') || undefined;\n var argsRaw;\n if (functionCall.match(/\\(([^)]*)\\)/)) {\n argsRaw = functionCall.replace(/^[^(]+\\(([^)]*)\\)$/, '$1');\n }\n var args = (argsRaw === undefined || argsRaw === '[arguments not available]') ?\n undefined : argsRaw.split(',');\n\n return new StackFrame({\n functionName: functionName,\n args: args,\n fileName: locationParts[0],\n lineNumber: locationParts[1],\n columnNumber: locationParts[2],\n source: line\n });\n }, this);\n }\n };\n}));\n", "var global = typeof self !== 'undefined' ? self : this;\nvar __self__ = (function () {\nfunction F() {\nthis.fetch = false;\nthis.DOMException = global.DOMException\n}\nF.prototype = global;\nreturn new F();\n})();\n(function(self) {\n\nvar irrelevant = (function (exports) {\n\n var support = {\n searchParams: 'URLSearchParams' in self,\n iterable: 'Symbol' in self && 'iterator' in Symbol,\n blob:\n 'FileReader' in self &&\n 'Blob' in self &&\n (function() {\n try {\n new Blob();\n return true\n } catch (e) {\n return false\n }\n })(),\n formData: 'FormData' in self,\n arrayBuffer: 'ArrayBuffer' in self\n };\n\n function isDataView(obj) {\n return obj && DataView.prototype.isPrototypeOf(obj)\n }\n\n if (support.arrayBuffer) {\n var viewClasses = [\n '[object Int8Array]',\n '[object Uint8Array]',\n '[object Uint8ClampedArray]',\n '[object Int16Array]',\n '[object Uint16Array]',\n '[object Int32Array]',\n '[object Uint32Array]',\n '[object Float32Array]',\n '[object Float64Array]'\n ];\n\n var isArrayBufferView =\n ArrayBuffer.isView ||\n function(obj) {\n return obj && viewClasses.indexOf(Object.prototype.toString.call(obj)) > -1\n };\n }\n\n function normalizeName(name) {\n if (typeof name !== 'string') {\n name = String(name);\n }\n if (/[^a-z0-9\\-#$%&'*+.^_`|~]/i.test(name)) {\n throw new TypeError('Invalid character in header field name')\n }\n return name.toLowerCase()\n }\n\n function normalizeValue(value) {\n if (typeof value !== 'string') {\n value = String(value);\n }\n return value\n }\n\n // Build a destructive iterator for the value list\n function iteratorFor(items) {\n var iterator = {\n next: function() {\n var value = items.shift();\n return {done: value === undefined, value: value}\n }\n };\n\n if (support.iterable) {\n iterator[Symbol.iterator] = function() {\n return iterator\n };\n }\n\n return iterator\n }\n\n function Headers(headers) {\n this.map = {};\n\n if (headers instanceof Headers) {\n headers.forEach(function(value, name) {\n this.append(name, value);\n }, this);\n } else if (Array.isArray(headers)) {\n headers.forEach(function(header) {\n this.append(header[0], header[1]);\n }, this);\n } else if (headers) {\n Object.getOwnPropertyNames(headers).forEach(function(name) {\n this.append(name, headers[name]);\n }, this);\n }\n }\n\n Headers.prototype.append = function(name, value) {\n name = normalizeName(name);\n value = normalizeValue(value);\n var oldValue = this.map[name];\n this.map[name] = oldValue ? oldValue + ', ' + value : value;\n };\n\n Headers.prototype['delete'] = function(name) {\n delete this.map[normalizeName(name)];\n };\n\n Headers.prototype.get = function(name) {\n name = normalizeName(name);\n return this.has(name) ? this.map[name] : null\n };\n\n Headers.prototype.has = function(name) {\n return this.map.hasOwnProperty(normalizeName(name))\n };\n\n Headers.prototype.set = function(name, value) {\n this.map[normalizeName(name)] = normalizeValue(value);\n };\n\n Headers.prototype.forEach = function(callback, thisArg) {\n for (var name in this.map) {\n if (this.map.hasOwnProperty(name)) {\n callback.call(thisArg, this.map[name], name, this);\n }\n }\n };\n\n Headers.prototype.keys = function() {\n var items = [];\n this.forEach(function(value, name) {\n items.push(name);\n });\n return iteratorFor(items)\n };\n\n Headers.prototype.values = function() {\n var items = [];\n this.forEach(function(value) {\n items.push(value);\n });\n return iteratorFor(items)\n };\n\n Headers.prototype.entries = function() {\n var items = [];\n this.forEach(function(value, name) {\n items.push([name, value]);\n });\n return iteratorFor(items)\n };\n\n if (support.iterable) {\n Headers.prototype[Symbol.iterator] = Headers.prototype.entries;\n }\n\n function consumed(body) {\n if (body.bodyUsed) {\n return Promise.reject(new TypeError('Already read'))\n }\n body.bodyUsed = true;\n }\n\n function fileReaderReady(reader) {\n return new Promise(function(resolve, reject) {\n reader.onload = function() {\n resolve(reader.result);\n };\n reader.onerror = function() {\n reject(reader.error);\n };\n })\n }\n\n function readBlobAsArrayBuffer(blob) {\n var reader = new FileReader();\n var promise = fileReaderReady(reader);\n reader.readAsArrayBuffer(blob);\n return promise\n }\n\n function readBlobAsText(blob) {\n var reader = new FileReader();\n var promise = fileReaderReady(reader);\n reader.readAsText(blob);\n return promise\n }\n\n function readArrayBufferAsText(buf) {\n var view = new Uint8Array(buf);\n var chars = new Array(view.length);\n\n for (var i = 0; i < view.length; i++) {\n chars[i] = String.fromCharCode(view[i]);\n }\n return chars.join('')\n }\n\n function bufferClone(buf) {\n if (buf.slice) {\n return buf.slice(0)\n } else {\n var view = new Uint8Array(buf.byteLength);\n view.set(new Uint8Array(buf));\n return view.buffer\n }\n }\n\n function Body() {\n this.bodyUsed = false;\n\n this._initBody = function(body) {\n this._bodyInit = body;\n if (!body) {\n this._bodyText = '';\n } else if (typeof body === 'string') {\n this._bodyText = body;\n } else if (support.blob && Blob.prototype.isPrototypeOf(body)) {\n this._bodyBlob = body;\n } else if (support.formData && FormData.prototype.isPrototypeOf(body)) {\n this._bodyFormData = body;\n } else if (support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) {\n this._bodyText = body.toString();\n } else if (support.arrayBuffer && support.blob && isDataView(body)) {\n this._bodyArrayBuffer = bufferClone(body.buffer);\n // IE 10-11 can't handle a DataView body.\n this._bodyInit = new Blob([this._bodyArrayBuffer]);\n } else if (support.arrayBuffer && (ArrayBuffer.prototype.isPrototypeOf(body) || isArrayBufferView(body))) {\n this._bodyArrayBuffer = bufferClone(body);\n } else {\n this._bodyText = body = Object.prototype.toString.call(body);\n }\n\n if (!this.headers.get('content-type')) {\n if (typeof body === 'string') {\n this.headers.set('content-type', 'text/plain;charset=UTF-8');\n } else if (this._bodyBlob && this._bodyBlob.type) {\n this.headers.set('content-type', this._bodyBlob.type);\n } else if (support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) {\n this.headers.set('content-type', 'application/x-www-form-urlencoded;charset=UTF-8');\n }\n }\n };\n\n if (support.blob) {\n this.blob = function() {\n var rejected = consumed(this);\n if (rejected) {\n return rejected\n }\n\n if (this._bodyBlob) {\n return Promise.resolve(this._bodyBlob)\n } else if (this._bodyArrayBuffer) {\n return Promise.resolve(new Blob([this._bodyArrayBuffer]))\n } else if (this._bodyFormData) {\n throw new Error('could not read FormData body as blob')\n } else {\n return Promise.resolve(new Blob([this._bodyText]))\n }\n };\n\n this.arrayBuffer = function() {\n if (this._bodyArrayBuffer) {\n return consumed(this) || Promise.resolve(this._bodyArrayBuffer)\n } else {\n return this.blob().then(readBlobAsArrayBuffer)\n }\n };\n }\n\n this.text = function() {\n var rejected = consumed(this);\n if (rejected) {\n return rejected\n }\n\n if (this._bodyBlob) {\n return readBlobAsText(this._bodyBlob)\n } else if (this._bodyArrayBuffer) {\n return Promise.resolve(readArrayBufferAsText(this._bodyArrayBuffer))\n } else if (this._bodyFormData) {\n throw new Error('could not read FormData body as text')\n } else {\n return Promise.resolve(this._bodyText)\n }\n };\n\n if (support.formData) {\n this.formData = function() {\n return this.text().then(decode)\n };\n }\n\n this.json = function() {\n return this.text().then(JSON.parse)\n };\n\n return this\n }\n\n // HTTP methods whose capitalization should be normalized\n var methods = ['DELETE', 'GET', 'HEAD', 'OPTIONS', 'POST', 'PUT'];\n\n function normalizeMethod(method) {\n var upcased = method.toUpperCase();\n return methods.indexOf(upcased) > -1 ? upcased : method\n }\n\n function Request(input, options) {\n options = options || {};\n var body = options.body;\n\n if (input instanceof Request) {\n if (input.bodyUsed) {\n throw new TypeError('Already read')\n }\n this.url = input.url;\n this.credentials = input.credentials;\n if (!options.headers) {\n this.headers = new Headers(input.headers);\n }\n this.method = input.method;\n this.mode = input.mode;\n this.signal = input.signal;\n if (!body && input._bodyInit != null) {\n body = input._bodyInit;\n input.bodyUsed = true;\n }\n } else {\n this.url = String(input);\n }\n\n this.credentials = options.credentials || this.credentials || 'same-origin';\n if (options.headers || !this.headers) {\n this.headers = new Headers(options.headers);\n }\n this.method = normalizeMethod(options.method || this.method || 'GET');\n this.mode = options.mode || this.mode || null;\n this.signal = options.signal || this.signal;\n this.referrer = null;\n\n if ((this.method === 'GET' || this.method === 'HEAD') && body) {\n throw new TypeError('Body not allowed for GET or HEAD requests')\n }\n this._initBody(body);\n }\n\n Request.prototype.clone = function() {\n return new Request(this, {body: this._bodyInit})\n };\n\n function decode(body) {\n var form = new FormData();\n body\n .trim()\n .split('&')\n .forEach(function(bytes) {\n if (bytes) {\n var split = bytes.split('=');\n var name = split.shift().replace(/\\+/g, ' ');\n var value = split.join('=').replace(/\\+/g, ' ');\n form.append(decodeURIComponent(name), decodeURIComponent(value));\n }\n });\n return form\n }\n\n function parseHeaders(rawHeaders) {\n var headers = new Headers();\n // Replace instances of \\r\\n and \\n followed by at least one space or horizontal tab with a space\n // https://tools.ietf.org/html/rfc7230#section-3.2\n var preProcessedHeaders = rawHeaders.replace(/\\r?\\n[\\t ]+/g, ' ');\n preProcessedHeaders.split(/\\r?\\n/).forEach(function(line) {\n var parts = line.split(':');\n var key = parts.shift().trim();\n if (key) {\n var value = parts.join(':').trim();\n headers.append(key, value);\n }\n });\n return headers\n }\n\n Body.call(Request.prototype);\n\n function Response(bodyInit, options) {\n if (!options) {\n options = {};\n }\n\n this.type = 'default';\n this.status = options.status === undefined ? 200 : options.status;\n this.ok = this.status >= 200 && this.status < 300;\n this.statusText = 'statusText' in options ? options.statusText : 'OK';\n this.headers = new Headers(options.headers);\n this.url = options.url || '';\n this._initBody(bodyInit);\n }\n\n Body.call(Response.prototype);\n\n Response.prototype.clone = function() {\n return new Response(this._bodyInit, {\n status: this.status,\n statusText: this.statusText,\n headers: new Headers(this.headers),\n url: this.url\n })\n };\n\n Response.error = function() {\n var response = new Response(null, {status: 0, statusText: ''});\n response.type = 'error';\n return response\n };\n\n var redirectStatuses = [301, 302, 303, 307, 308];\n\n Response.redirect = function(url, status) {\n if (redirectStatuses.indexOf(status) === -1) {\n throw new RangeError('Invalid status code')\n }\n\n return new Response(null, {status: status, headers: {location: url}})\n };\n\n exports.DOMException = self.DOMException;\n try {\n new exports.DOMException();\n } catch (err) {\n exports.DOMException = function(message, name) {\n this.message = message;\n this.name = name;\n var error = Error(message);\n this.stack = error.stack;\n };\n exports.DOMException.prototype = Object.create(Error.prototype);\n exports.DOMException.prototype.constructor = exports.DOMException;\n }\n\n function fetch(input, init) {\n return new Promise(function(resolve, reject) {\n var request = new Request(input, init);\n\n if (request.signal && request.signal.aborted) {\n return reject(new exports.DOMException('Aborted', 'AbortError'))\n }\n\n var xhr = new XMLHttpRequest();\n\n function abortXhr() {\n xhr.abort();\n }\n\n xhr.onload = function() {\n var options = {\n status: xhr.status,\n statusText: xhr.statusText,\n headers: parseHeaders(xhr.getAllResponseHeaders() || '')\n };\n options.url = 'responseURL' in xhr ? xhr.responseURL : options.headers.get('X-Request-URL');\n var body = 'response' in xhr ? xhr.response : xhr.responseText;\n resolve(new Response(body, options));\n };\n\n xhr.onerror = function() {\n reject(new TypeError('Network request failed'));\n };\n\n xhr.ontimeout = function() {\n reject(new TypeError('Network request failed'));\n };\n\n xhr.onabort = function() {\n reject(new exports.DOMException('Aborted', 'AbortError'));\n };\n\n xhr.open(request.method, request.url, true);\n\n if (request.credentials === 'include') {\n xhr.withCredentials = true;\n } else if (request.credentials === 'omit') {\n xhr.withCredentials = false;\n }\n\n if ('responseType' in xhr && support.blob) {\n xhr.responseType = 'blob';\n }\n\n request.headers.forEach(function(value, name) {\n xhr.setRequestHeader(name, value);\n });\n\n if (request.signal) {\n request.signal.addEventListener('abort', abortXhr);\n\n xhr.onreadystatechange = function() {\n // DONE (success or failure)\n if (xhr.readyState === 4) {\n request.signal.removeEventListener('abort', abortXhr);\n }\n };\n }\n\n xhr.send(typeof request._bodyInit === 'undefined' ? null : request._bodyInit);\n })\n }\n\n fetch.polyfill = true;\n\n if (!self.fetch) {\n self.fetch = fetch;\n self.Headers = Headers;\n self.Request = Request;\n self.Response = Response;\n }\n\n exports.Headers = Headers;\n exports.Request = Request;\n exports.Response = Response;\n exports.fetch = fetch;\n\n Object.defineProperty(exports, '__esModule', { value: true });\n\n return exports;\n\n})({});\n})(__self__);\n__self__.fetch.ponyfill = true;\n// Remove \"polyfill\" property added by whatwg-fetch\ndelete __self__.fetch.polyfill;\n// Choose between native implementation (global) or custom implementation (__self__)\n// var ctx = global.fetch ? global : __self__;\nvar ctx = __self__; // this line disable service worker support temporarily\nexports = ctx.fetch // To enable: import fetch from 'cross-fetch'\nexports.default = ctx.fetch // For TypeScript consumers without esModuleInterop.\nexports.fetch = ctx.fetch // To enable: import {fetch} from 'cross-fetch'\nexports.Headers = ctx.Headers\nexports.Request = ctx.Request\nexports.Response = ctx.Response\nmodule.exports = exports\n", "\nfunction TreeBase() {}\n\n// removes all nodes from the tree\nTreeBase.prototype.clear = function() {\n this._root = null;\n this.size = 0;\n};\n\n// returns node data if found, null otherwise\nTreeBase.prototype.find = function(data) {\n var res = this._root;\n\n while(res !== null) {\n var c = this._comparator(data, res.data);\n if(c === 0) {\n return res.data;\n }\n else {\n res = res.get_child(c > 0);\n }\n }\n\n return null;\n};\n\n// returns iterator to node if found, null otherwise\nTreeBase.prototype.findIter = function(data) {\n var res = this._root;\n var iter = this.iterator();\n\n while(res !== null) {\n var c = this._comparator(data, res.data);\n if(c === 0) {\n iter._cursor = res;\n return iter;\n }\n else {\n iter._ancestors.push(res);\n res = res.get_child(c > 0);\n }\n }\n\n return null;\n};\n\n// Returns an iterator to the tree node at or immediately after the item\nTreeBase.prototype.lowerBound = function(item) {\n var cur = this._root;\n var iter = this.iterator();\n var cmp = this._comparator;\n\n while(cur !== null) {\n var c = cmp(item, cur.data);\n if(c === 0) {\n iter._cursor = cur;\n return iter;\n }\n iter._ancestors.push(cur);\n cur = cur.get_child(c > 0);\n }\n\n for(var i=iter._ancestors.length - 1; i >= 0; --i) {\n cur = iter._ancestors[i];\n if(cmp(item, cur.data) < 0) {\n iter._cursor = cur;\n iter._ancestors.length = i;\n return iter;\n }\n }\n\n iter._ancestors.length = 0;\n return iter;\n};\n\n// Returns an iterator to the tree node immediately after the item\nTreeBase.prototype.upperBound = function(item) {\n var iter = this.lowerBound(item);\n var cmp = this._comparator;\n\n while(iter.data() !== null && cmp(iter.data(), item) === 0) {\n iter.next();\n }\n\n return iter;\n};\n\n// returns null if tree is empty\nTreeBase.prototype.min = function() {\n var res = this._root;\n if(res === null) {\n return null;\n }\n\n while(res.left !== null) {\n res = res.left;\n }\n\n return res.data;\n};\n\n// returns null if tree is empty\nTreeBase.prototype.max = function() {\n var res = this._root;\n if(res === null) {\n return null;\n }\n\n while(res.right !== null) {\n res = res.right;\n }\n\n return res.data;\n};\n\n// returns a null iterator\n// call next() or prev() to point to an element\nTreeBase.prototype.iterator = function() {\n return new Iterator(this);\n};\n\n// calls cb on each node's data, in order\nTreeBase.prototype.each = function(cb) {\n var it=this.iterator(), data;\n while((data = it.next()) !== null) {\n if(cb(data) === false) {\n return;\n }\n }\n};\n\n// calls cb on each node's data, in reverse order\nTreeBase.prototype.reach = function(cb) {\n var it=this.iterator(), data;\n while((data = it.prev()) !== null) {\n if(cb(data) === false) {\n return;\n }\n }\n};\n\n\nfunction Iterator(tree) {\n this._tree = tree;\n this._ancestors = [];\n this._cursor = null;\n}\n\nIterator.prototype.data = function() {\n return this._cursor !== null ? this._cursor.data : null;\n};\n\n// if null-iterator, returns first node\n// otherwise, returns next node\nIterator.prototype.next = function() {\n if(this._cursor === null) {\n var root = this._tree._root;\n if(root !== null) {\n this._minNode(root);\n }\n }\n else {\n if(this._cursor.right === null) {\n // no greater node in subtree, go up to parent\n // if coming from a right child, continue up the stack\n var save;\n do {\n save = this._cursor;\n if(this._ancestors.length) {\n this._cursor = this._ancestors.pop();\n }\n else {\n this._cursor = null;\n break;\n }\n } while(this._cursor.right === save);\n }\n else {\n // get the next node from the subtree\n this._ancestors.push(this._cursor);\n this._minNode(this._cursor.right);\n }\n }\n return this._cursor !== null ? this._cursor.data : null;\n};\n\n// if null-iterator, returns last node\n// otherwise, returns previous node\nIterator.prototype.prev = function() {\n if(this._cursor === null) {\n var root = this._tree._root;\n if(root !== null) {\n this._maxNode(root);\n }\n }\n else {\n if(this._cursor.left === null) {\n var save;\n do {\n save = this._cursor;\n if(this._ancestors.length) {\n this._cursor = this._ancestors.pop();\n }\n else {\n this._cursor = null;\n break;\n }\n } while(this._cursor.left === save);\n }\n else {\n this._ancestors.push(this._cursor);\n this._maxNode(this._cursor.left);\n }\n }\n return this._cursor !== null ? this._cursor.data : null;\n};\n\nIterator.prototype._minNode = function(start) {\n while(start.left !== null) {\n this._ancestors.push(start);\n start = start.left;\n }\n this._cursor = start;\n};\n\nIterator.prototype._maxNode = function(start) {\n while(start.right !== null) {\n this._ancestors.push(start);\n start = start.right;\n }\n this._cursor = start;\n};\n\nmodule.exports = TreeBase;\n\n", "\nvar TreeBase = require('./treebase');\n\nfunction Node(data) {\n this.data = data;\n this.left = null;\n this.right = null;\n this.red = true;\n}\n\nNode.prototype.get_child = function(dir) {\n return dir ? this.right : this.left;\n};\n\nNode.prototype.set_child = function(dir, val) {\n if(dir) {\n this.right = val;\n }\n else {\n this.left = val;\n }\n};\n\nfunction RBTree(comparator) {\n this._root = null;\n this._comparator = comparator;\n this.size = 0;\n}\n\nRBTree.prototype = new TreeBase();\n\n// returns true if inserted, false if duplicate\nRBTree.prototype.insert = function(data) {\n var ret = false;\n\n if(this._root === null) {\n // empty tree\n this._root = new Node(data);\n ret = true;\n this.size++;\n }\n else {\n var head = new Node(undefined); // fake tree root\n\n var dir = 0;\n var last = 0;\n\n // setup\n var gp = null; // grandparent\n var ggp = head; // grand-grand-parent\n var p = null; // parent\n var node = this._root;\n ggp.right = this._root;\n\n // search down\n while(true) {\n if(node === null) {\n // insert new node at the bottom\n node = new Node(data);\n p.set_child(dir, node);\n ret = true;\n this.size++;\n }\n else if(is_red(node.left) && is_red(node.right)) {\n // color flip\n node.red = true;\n node.left.red = false;\n node.right.red = false;\n }\n\n // fix red violation\n if(is_red(node) && is_red(p)) {\n var dir2 = ggp.right === gp;\n\n if(node === p.get_child(last)) {\n ggp.set_child(dir2, single_rotate(gp, !last));\n }\n else {\n ggp.set_child(dir2, double_rotate(gp, !last));\n }\n }\n\n var cmp = this._comparator(node.data, data);\n\n // stop if found\n if(cmp === 0) {\n break;\n }\n\n last = dir;\n dir = cmp < 0;\n\n // update helpers\n if(gp !== null) {\n ggp = gp;\n }\n gp = p;\n p = node;\n node = node.get_child(dir);\n }\n\n // update root\n this._root = head.right;\n }\n\n // make root black\n this._root.red = false;\n\n return ret;\n};\n\n// returns true if removed, false if not found\nRBTree.prototype.remove = function(data) {\n if(this._root === null) {\n return false;\n }\n\n var head = new Node(undefined); // fake tree root\n var node = head;\n node.right = this._root;\n var p = null; // parent\n var gp = null; // grand parent\n var found = null; // found item\n var dir = 1;\n\n while(node.get_child(dir) !== null) {\n var last = dir;\n\n // update helpers\n gp = p;\n p = node;\n node = node.get_child(dir);\n\n var cmp = this._comparator(data, node.data);\n\n dir = cmp > 0;\n\n // save found node\n if(cmp === 0) {\n found = node;\n }\n\n // push the red node down\n if(!is_red(node) && !is_red(node.get_child(dir))) {\n if(is_red(node.get_child(!dir))) {\n var sr = single_rotate(node, dir);\n p.set_child(last, sr);\n p = sr;\n }\n else if(!is_red(node.get_child(!dir))) {\n var sibling = p.get_child(!last);\n if(sibling !== null) {\n if(!is_red(sibling.get_child(!last)) && !is_red(sibling.get_child(last))) {\n // color flip\n p.red = false;\n sibling.red = true;\n node.red = true;\n }\n else {\n var dir2 = gp.right === p;\n\n if(is_red(sibling.get_child(last))) {\n gp.set_child(dir2, double_rotate(p, last));\n }\n else if(is_red(sibling.get_child(!last))) {\n gp.set_child(dir2, single_rotate(p, last));\n }\n\n // ensure correct coloring\n var gpc = gp.get_child(dir2);\n gpc.red = true;\n node.red = true;\n gpc.left.red = false;\n gpc.right.red = false;\n }\n }\n }\n }\n }\n\n // replace and remove if found\n if(found !== null) {\n found.data = node.data;\n p.set_child(p.right === node, node.get_child(node.left === null));\n this.size--;\n }\n\n // update root and make it black\n this._root = head.right;\n if(this._root !== null) {\n this._root.red = false;\n }\n\n return found !== null;\n};\n\nfunction is_red(node) {\n return node !== null && node.red;\n}\n\nfunction single_rotate(root, dir) {\n var save = root.get_child(!dir);\n\n root.set_child(!dir, save.get_child(dir));\n save.set_child(dir, root);\n\n root.red = true;\n save.red = false;\n\n return save;\n}\n\nfunction double_rotate(root, dir) {\n root.set_child(!dir, single_rotate(root.get_child(!dir), !dir));\n return single_rotate(root, dir);\n}\n\nmodule.exports = RBTree;\n", "\nvar TreeBase = require('./treebase');\n\nfunction Node(data) {\n this.data = data;\n this.left = null;\n this.right = null;\n}\n\nNode.prototype.get_child = function(dir) {\n return dir ? this.right : this.left;\n};\n\nNode.prototype.set_child = function(dir, val) {\n if(dir) {\n this.right = val;\n }\n else {\n this.left = val;\n }\n};\n\nfunction BinTree(comparator) {\n this._root = null;\n this._comparator = comparator;\n this.size = 0;\n}\n\nBinTree.prototype = new TreeBase();\n\n// returns true if inserted, false if duplicate\nBinTree.prototype.insert = function(data) {\n if(this._root === null) {\n // empty tree\n this._root = new Node(data);\n this.size++;\n return true;\n }\n\n var dir = 0;\n\n // setup\n var p = null; // parent\n var node = this._root;\n\n // search down\n while(true) {\n if(node === null) {\n // insert new node at the bottom\n node = new Node(data);\n p.set_child(dir, node);\n ret = true;\n this.size++;\n return true;\n }\n\n // stop if found\n if(this._comparator(node.data, data) === 0) {\n return false;\n }\n\n dir = this._comparator(node.data, data) < 0;\n\n // update helpers\n p = node;\n node = node.get_child(dir);\n }\n};\n\n// returns true if removed, false if not found\nBinTree.prototype.remove = function(data) {\n if(this._root === null) {\n return false;\n }\n\n var head = new Node(undefined); // fake tree root\n var node = head;\n node.right = this._root;\n var p = null; // parent\n var found = null; // found item\n var dir = 1;\n\n while(node.get_child(dir) !== null) {\n p = node;\n node = node.get_child(dir);\n var cmp = this._comparator(data, node.data);\n dir = cmp > 0;\n\n if(cmp === 0) {\n found = node;\n }\n }\n\n if(found !== null) {\n found.data = node.data;\n p.set_child(p.right === node, node.get_child(node.left === null));\n\n this._root = head.right;\n this.size--;\n return true;\n }\n else {\n return false;\n }\n};\n\nmodule.exports = BinTree;\n\n", "module.exports = {\n RBTree: require('./lib/rbtree'),\n BinTree: require('./lib/bintree')\n};\n", "//\n// TDigest:\n//\n// approximate distribution percentiles from a stream of reals\n//\nvar RBTree = require('bintrees').RBTree;\n\nfunction TDigest(delta, K, CX) {\n // allocate a TDigest structure.\n //\n // delta is the compression factor, the max fraction of mass that\n // can be owned by one centroid (bigger, up to 1.0, means more\n // compression). delta=false switches off TDigest behavior and treats\n // the distribution as discrete, with no merging and exact values\n // reported.\n //\n // K is a size threshold that triggers recompression as the TDigest\n // grows during input. (Set it to 0 to disable automatic recompression)\n //\n // CX specifies how often to update cached cumulative totals used\n // for quantile estimation during ingest (see cumulate()). Set to\n // 0 to use exact quantiles for each new point.\n //\n this.discrete = (delta === false);\n this.delta = delta || 0.01;\n this.K = (K === undefined) ? 25 : K;\n this.CX = (CX === undefined) ? 1.1 : CX;\n this.centroids = new RBTree(compare_centroid_means);\n this.nreset = 0;\n this.reset();\n}\n\nTDigest.prototype.reset = function() {\n // prepare to digest new points.\n //\n this.centroids.clear();\n this.n = 0;\n this.nreset += 1;\n this.last_cumulate = 0;\n};\n\nTDigest.prototype.size = function() {\n return this.centroids.size;\n};\n\nTDigest.prototype.toArray = function(everything) {\n // return {mean,n} of centroids as an array ordered by mean.\n //\n var result = [];\n if (everything) {\n this._cumulate(true); // be sure cumns are exact\n this.centroids.each(function(c) { result.push(c); });\n } else {\n this.centroids.each(function(c) { result.push({mean:c.mean, n:c.n}); });\n }\n return result;\n};\n\nTDigest.prototype.summary = function() {\n var approx = (this.discrete) ? \"exact \" : \"approximating \";\n var s = [approx + this.n + \" samples using \" + this.size() + \" centroids\",\n \"min = \"+this.percentile(0),\n \"Q1 = \"+this.percentile(0.25),\n \"Q2 = \"+this.percentile(0.5),\n \"Q3 = \"+this.percentile(0.75),\n \"max = \"+this.percentile(1.0)];\n return s.join('\\n');\n};\n\nfunction compare_centroid_means(a, b) {\n // order two centroids by mean.\n //\n return (a.mean > b.mean) ? 1 : (a.mean < b.mean) ? -1 : 0;\n}\n\nfunction compare_centroid_mean_cumns(a, b) {\n // order two centroids by mean_cumn.\n //\n return (a.mean_cumn - b.mean_cumn);\n}\n\nTDigest.prototype.push = function(x, n) {\n // incorporate value or array of values x, having count n into the\n // TDigest. n defaults to 1.\n //\n n = n || 1;\n x = Array.isArray(x) ? x : [x];\n for (var i = 0 ; i < x.length ; i++) {\n this._digest(x[i], n);\n }\n};\n\nTDigest.prototype.push_centroid = function(c) {\n // incorporate centroid or array of centroids c\n //\n c = Array.isArray(c) ? c : [c];\n for (var i = 0 ; i < c.length ; i++) {\n this._digest(c[i].mean, c[i].n);\n }\n};\n\nTDigest.prototype._cumulate = function(exact) {\n // update cumulative counts for each centroid\n //\n // exact: falsey means only cumulate after sufficient\n // growth. During ingest, these counts are used as quantile\n // estimates, and they work well even when somewhat out of\n // date. (this is a departure from the publication, you may set CX\n // to 0 to disable).\n //\n if (this.n === this.last_cumulate ||\n !exact && this.CX && this.CX > (this.n / this.last_cumulate)) {\n return;\n }\n var cumn = 0;\n this.centroids.each(function(c) {\n c.mean_cumn = cumn + c.n / 2; // half of n at the mean\n cumn = c.cumn = cumn + c.n;\n });\n this.n = this.last_cumulate = cumn;\n};\n\nTDigest.prototype.find_nearest = function(x) {\n // find the centroid closest to x. The assumption of\n // unique means and a unique nearest centroid departs from the\n // paper, see _digest() below\n //\n if (this.size() === 0) {\n return null;\n }\n var iter = this.centroids.lowerBound({mean:x}); // x <= iter || iter==null\n var c = (iter.data() === null) ? iter.prev() : iter.data();\n if (c.mean === x || this.discrete) {\n return c; // c is either x or a neighbor (discrete: no distance func)\n }\n var prev = iter.prev();\n if (prev && Math.abs(prev.mean - x) < Math.abs(c.mean - x)) {\n return prev;\n } else {\n return c;\n }\n};\n\nTDigest.prototype._new_centroid = function(x, n, cumn) {\n // create and insert a new centroid into the digest (don't update\n // cumulatives).\n //\n var c = {mean:x, n:n, cumn:cumn};\n this.centroids.insert(c);\n this.n += n;\n return c;\n};\n\nTDigest.prototype._addweight = function(nearest, x, n) {\n // add weight at location x to nearest centroid. adding x to\n // nearest will not shift its relative position in the tree and\n // require reinsertion.\n //\n if (x !== nearest.mean) {\n nearest.mean += n * (x - nearest.mean) / (nearest.n + n);\n }\n nearest.cumn += n;\n nearest.mean_cumn += n / 2;\n nearest.n += n;\n this.n += n;\n};\n\nTDigest.prototype._digest = function(x, n) {\n // incorporate value x, having count n into the TDigest.\n //\n var min = this.centroids.min();\n var max = this.centroids.max();\n var nearest = this.find_nearest(x);\n if (nearest && nearest.mean === x) {\n // accumulate exact matches into the centroid without\n // limit. this is a departure from the paper, made so\n // centroids remain unique and code can be simple.\n this._addweight(nearest, x, n);\n } else if (nearest === min) {\n this._new_centroid(x, n, 0); // new point around min boundary\n } else if (nearest === max ) {\n this._new_centroid(x, n, this.n); // new point around max boundary\n } else if (this.discrete) {\n this._new_centroid(x, n, nearest.cumn); // never merge\n } else {\n // conider a merge based on nearest centroid's capacity. if\n // there's not room for all of n, don't bother merging any of\n // it into nearest, as we'll have to make a new centroid\n // anyway for the remainder (departure from the paper).\n var p = nearest.mean_cumn / this.n;\n var max_n = Math.floor(4 * this.n * this.delta * p * (1 - p));\n if (max_n - nearest.n >= n) {\n this._addweight(nearest, x, n);\n } else {\n this._new_centroid(x, n, nearest.cumn);\n }\n }\n this._cumulate(false);\n if (!this.discrete && this.K && this.size() > this.K / this.delta) {\n // re-process the centroids and hope for some compression.\n this.compress();\n }\n};\n\nTDigest.prototype.bound_mean = function(x) {\n // find centroids lower and upper such that lower.mean < x <\n // upper.mean or lower.mean === x === upper.mean. Don't call\n // this for x out of bounds.\n //\n var iter = this.centroids.upperBound({mean:x}); // x < iter\n var lower = iter.prev(); // lower <= x\n var upper = (lower.mean === x) ? lower : iter.next();\n return [lower, upper];\n};\n\nTDigest.prototype.p_rank = function(x_or_xlist) {\n // return approximate percentile-ranks (0..1) for data value x.\n // or list of x. calculated according to\n // https://en.wikipedia.org/wiki/Percentile_rank\n //\n // (Note that in continuous mode, boundary sample values will\n // report half their centroid weight inward from 0/1 as the\n // percentile-rank. X values outside the observed range return\n // 0/1)\n //\n // this triggers cumulate() if cumn's are out of date.\n //\n var xs = Array.isArray(x_or_xlist) ? x_or_xlist : [x_or_xlist];\n var ps = xs.map(this._p_rank, this);\n return Array.isArray(x_or_xlist) ? ps : ps[0];\n};\n\nTDigest.prototype._p_rank = function(x) {\n if (this.size() === 0) {\n return undefined;\n } else if (x < this.centroids.min().mean) {\n return 0.0;\n } else if (x > this.centroids.max().mean) {\n return 1.0;\n }\n // find centroids that bracket x and interpolate x's cumn from\n // their cumn's.\n this._cumulate(true); // be sure cumns are exact\n var bound = this.bound_mean(x);\n var lower = bound[0], upper = bound[1];\n if (this.discrete) {\n return lower.cumn / this.n;\n } else {\n var cumn = lower.mean_cumn;\n if (lower !== upper) {\n cumn += (x - lower.mean) * (upper.mean_cumn - lower.mean_cumn) / (upper.mean - lower.mean);\n }\n return cumn / this.n;\n }\n};\n\nTDigest.prototype.bound_mean_cumn = function(cumn) {\n // find centroids lower and upper such that lower.mean_cumn < x <\n // upper.mean_cumn or lower.mean_cumn === x === upper.mean_cumn. Don't call\n // this for cumn out of bounds.\n //\n // XXX because mean and mean_cumn give rise to the same sort order\n // (up to identical means), use the mean rbtree for our search.\n this.centroids._comparator = compare_centroid_mean_cumns;\n var iter = this.centroids.upperBound({mean_cumn:cumn}); // cumn < iter\n this.centroids._comparator = compare_centroid_means;\n var lower = iter.prev(); // lower <= cumn\n var upper = (lower && lower.mean_cumn === cumn) ? lower : iter.next();\n return [lower, upper];\n};\n\nTDigest.prototype.percentile = function(p_or_plist) {\n // for percentage p (0..1), or for each p in a list of ps, return\n // the smallest data value q at which at least p percent of the\n // observations <= q.\n //\n // for discrete distributions, this selects q using the Nearest\n // Rank Method\n // (https://en.wikipedia.org/wiki/Percentile#The_Nearest_Rank_method)\n // (in scipy, same as percentile(...., interpolation='higher')\n //\n // for continuous distributions, interpolates data values between\n // count-weighted bracketing means.\n //\n // this triggers cumulate() if cumn's are out of date.\n //\n var ps = Array.isArray(p_or_plist) ? p_or_plist : [p_or_plist];\n var qs = ps.map(this._percentile, this);\n return Array.isArray(p_or_plist) ? qs : qs[0];\n};\n\nTDigest.prototype._percentile = function(p) {\n if (this.size() === 0) {\n return undefined;\n }\n this._cumulate(true); // be sure cumns are exact\n var h = this.n * p;\n var bound = this.bound_mean_cumn(h);\n var lower = bound[0], upper = bound[1];\n\n if (upper === lower || lower === null || upper === null) {\n return (lower || upper).mean;\n } else if (!this.discrete) {\n return lower.mean + (h - lower.mean_cumn) * (upper.mean - lower.mean) / (upper.mean_cumn - lower.mean_cumn);\n } else if (h <= lower.cumn) {\n return lower.mean;\n } else {\n return upper.mean;\n }\n};\n\nfunction pop_random(choices) {\n // remove and return an item randomly chosen from the array of choices\n // (mutates choices)\n //\n var idx = Math.floor(Math.random() * choices.length);\n return choices.splice(idx, 1)[0];\n}\n\nTDigest.prototype.compress = function() {\n // TDigests experience worst case compression (none) when input\n // increases monotonically. Improve on any bad luck by\n // reconsuming digest centroids as if they were weighted points\n // while shuffling their order (and hope for the best).\n //\n if (this.compressing) {\n return;\n }\n var points = this.toArray();\n this.reset();\n this.compressing = true;\n while (points.length > 0) {\n this.push_centroid(pop_random(points));\n }\n this._cumulate(true);\n this.compressing = false;\n};\n\nfunction Digest(config) {\n // allocate a distribution digest structure. This is an extension\n // of a TDigest structure that starts in exact histogram (discrete)\n // mode, and automatically switches to TDigest mode for large\n // samples that appear to be from a continuous distribution.\n //\n this.config = config || {};\n this.mode = this.config.mode || 'auto'; // disc, cont, auto\n TDigest.call(this, this.mode === 'cont' ? config.delta : false);\n this.digest_ratio = this.config.ratio || 0.9;\n this.digest_thresh = this.config.thresh || 1000;\n this.n_unique = 0;\n}\nDigest.prototype = Object.create(TDigest.prototype);\nDigest.prototype.constructor = Digest;\n\nDigest.prototype.push = function(x_or_xlist) {\n TDigest.prototype.push.call(this, x_or_xlist);\n this.check_continuous();\n};\n\nDigest.prototype._new_centroid = function(x, n, cumn) {\n this.n_unique += 1;\n TDigest.prototype._new_centroid.call(this, x, n, cumn);\n};\n\nDigest.prototype._addweight = function(nearest, x, n) {\n if (nearest.n === 1) {\n this.n_unique -= 1;\n }\n TDigest.prototype._addweight.call(this, nearest, x, n);\n};\n\nDigest.prototype.check_continuous = function() {\n // while in 'auto' mode, if there are many unique elements, assume\n // they are from a continuous distribution and switch to 'cont'\n // mode (tdigest behavior). Return true on transition from\n // disctete to continuous.\n if (this.mode !== 'auto' || this.size() < this.digest_thresh) {\n return false;\n }\n if (this.n_unique / this.size() > this.digest_ratio) {\n this.mode = 'cont';\n this.discrete = false;\n this.delta = this.config.delta || 0.01;\n this.compress();\n return true;\n }\n return false;\n};\n\nmodule.exports = {\n 'TDigest': TDigest,\n 'Digest': Digest\n};\n", "import DeviceDetector from \"device-detector-js\";\nimport { Notifier } from \"@airbrake/browser\";\n\nwindow.deviceDetector = new DeviceDetector();\nwindow.device = window.deviceDetector.parse(navigator.userAgent);\n\ntry {\n if (!window.device.bot && window.env.JEKYLL_ENV === \"production\") {\n window.airbrake = new Notifier({\n projectId: window.env.AIRBRAKE_PROJECT_ID,\n projectKey: window.env.AIRBRAKE_PROJECT_KEY,\n host: \"https://panel.sutty.nl\",\n });\n\n console.originalError = console.error;\n console.error = (...e) => {\n window.airbrake.notify(e.join(\" \"));\n return console.originalError(...e);\n };\n }\n} catch (e) {\n console.error(e);\n}\n\nimport * as Turbo from \"@hotwired/turbo\";\nTurbo.start();\n\nimport { Application } from \"@hotwired/stimulus\";\nwindow.Stimulus = Application.start();\nwindow.abortController = undefined;\n\nimport BodyScrollController from \"./controllers/body_scroll_controller\";\nimport DeviceDetectorController from \"./controllers/device_detector_controller\";\nimport ButtonCopyController from \"./controllers/button_copy_controller\";\nimport FootnotesController from \"./controllers/footnotes_controller\";\nimport SupportUsController from \"./controllers/support_us_controller\";\nimport ModalController from \"./controllers/modal_controller\";\nimport AuthorizeInteractionController from \"./controllers/authorize_interaction_controller\";\nimport TabsController from \"./controllers/tabs_controller\";\n\nStimulus.debug = (window.env.JEKYLL_ENV !== \"production\");\nStimulus.register(\"body-scroll\", BodyScrollController);\nStimulus.register(\"device-detector\", DeviceDetectorController);\nStimulus.register(\"button-copy\", ButtonCopyController);\nStimulus.register(\"footnotes\", FootnotesController);\nStimulus.register(\"support-us\", SupportUsController);\nStimulus.register(\"modal\", ModalController);\nStimulus.register(\"authorize-interaction\", AuthorizeInteractionController);\nStimulus.register(\"tabs\", TabsController);\n\ndocument.addEventListener(\"turbo:load\", (event) => {\n window.abortController = new AbortController();\n});\n\ndocument.addEventListener(\"turbo:visit\", (event) => {\n window.abortController.abort();\n});\n", "/**\n * @this {Promise}\n */\nfunction finallyConstructor(callback) {\n var constructor = this.constructor;\n return this.then(\n function(value) {\n // @ts-ignore\n return constructor.resolve(callback()).then(function() {\n return value;\n });\n },\n function(reason) {\n // @ts-ignore\n return constructor.resolve(callback()).then(function() {\n // @ts-ignore\n return constructor.reject(reason);\n });\n }\n );\n}\n\nexport default finallyConstructor;\n", "function allSettled(arr) {\n var P = this;\n return new P(function(resolve, reject) {\n if (!(arr && typeof arr.length !== 'undefined')) {\n return reject(\n new TypeError(\n typeof arr +\n ' ' +\n arr +\n ' is not iterable(cannot read property Symbol(Symbol.iterator))'\n )\n );\n }\n var args = Array.prototype.slice.call(arr);\n if (args.length === 0) return resolve([]);\n var remaining = args.length;\n\n function res(i, val) {\n if (val && (typeof val === 'object' || typeof val === 'function')) {\n var then = val.then;\n if (typeof then === 'function') {\n then.call(\n val,\n function(val) {\n res(i, val);\n },\n function(e) {\n args[i] = { status: 'rejected', reason: e };\n if (--remaining === 0) {\n resolve(args);\n }\n }\n );\n return;\n }\n }\n args[i] = { status: 'fulfilled', value: val };\n if (--remaining === 0) {\n resolve(args);\n }\n }\n\n for (var i = 0; i < args.length; i++) {\n res(i, args[i]);\n }\n });\n}\n\nexport default allSettled;\n", "/**\n * @constructor\n */\nfunction AggregateError(errors, message) {\n (this.name = 'AggregateError'), (this.errors = errors);\n this.message = message || '';\n}\nAggregateError.prototype = Error.prototype;\n\nfunction any(arr) {\n var P = this;\n return new P(function(resolve, reject) {\n if (!(arr && typeof arr.length !== 'undefined')) {\n return reject(new TypeError('Promise.any accepts an array'));\n }\n\n var args = Array.prototype.slice.call(arr);\n if (args.length === 0) return reject();\n\n var rejectionReasons = [];\n for (var i = 0; i < args.length; i++) {\n try {\n P.resolve(args[i])\n .then(resolve)\n .catch(function(error) {\n rejectionReasons.push(error);\n if (rejectionReasons.length === args.length) {\n reject(\n new AggregateError(\n rejectionReasons,\n 'All promises were rejected'\n )\n );\n }\n });\n } catch (ex) {\n reject(ex);\n }\n }\n });\n}\n\nexport default any;\n", "import promiseFinally from './finally';\nimport allSettled from './allSettled';\nimport any from './any';\n\n// Store setTimeout reference so promise-polyfill will be unaffected by\n// other code modifying setTimeout (like sinon.useFakeTimers())\nvar setTimeoutFunc = setTimeout;\n\nfunction isArray(x) {\n return Boolean(x && typeof x.length !== 'undefined');\n}\n\nfunction noop() {}\n\n// Polyfill for Function.prototype.bind\nfunction bind(fn, thisArg) {\n return function() {\n fn.apply(thisArg, arguments);\n };\n}\n\n/**\n * @constructor\n * @param {Function} fn\n */\nfunction Promise(fn) {\n if (!(this instanceof Promise))\n throw new TypeError('Promises must be constructed via new');\n if (typeof fn !== 'function') throw new TypeError('not a function');\n /** @type {!number} */\n this._state = 0;\n /** @type {!boolean} */\n this._handled = false;\n /** @type {Promise|undefined} */\n this._value = undefined;\n /** @type {!Array} */\n this._deferreds = [];\n\n doResolve(fn, this);\n}\n\nfunction handle(self, deferred) {\n while (self._state === 3) {\n self = self._value;\n }\n if (self._state === 0) {\n self._deferreds.push(deferred);\n return;\n }\n self._handled = true;\n Promise._immediateFn(function() {\n var cb = self._state === 1 ? deferred.onFulfilled : deferred.onRejected;\n if (cb === null) {\n (self._state === 1 ? resolve : reject)(deferred.promise, self._value);\n return;\n }\n var ret;\n try {\n ret = cb(self._value);\n } catch (e) {\n reject(deferred.promise, e);\n return;\n }\n resolve(deferred.promise, ret);\n });\n}\n\nfunction resolve(self, newValue) {\n try {\n // Promise Resolution Procedure: https://github.com/promises-aplus/promises-spec#the-promise-resolution-procedure\n if (newValue === self)\n throw new TypeError('A promise cannot be resolved with itself.');\n if (\n newValue &&\n (typeof newValue === 'object' || typeof newValue === 'function')\n ) {\n var then = newValue.then;\n if (newValue instanceof Promise) {\n self._state = 3;\n self._value = newValue;\n finale(self);\n return;\n } else if (typeof then === 'function') {\n doResolve(bind(then, newValue), self);\n return;\n }\n }\n self._state = 1;\n self._value = newValue;\n finale(self);\n } catch (e) {\n reject(self, e);\n }\n}\n\nfunction reject(self, newValue) {\n self._state = 2;\n self._value = newValue;\n finale(self);\n}\n\nfunction finale(self) {\n if (self._state === 2 && self._deferreds.length === 0) {\n Promise._immediateFn(function() {\n if (!self._handled) {\n Promise._unhandledRejectionFn(self._value);\n }\n });\n }\n\n for (var i = 0, len = self._deferreds.length; i < len; i++) {\n handle(self, self._deferreds[i]);\n }\n self._deferreds = null;\n}\n\n/**\n * @constructor\n */\nfunction Handler(onFulfilled, onRejected, promise) {\n this.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null;\n this.onRejected = typeof onRejected === 'function' ? onRejected : null;\n this.promise = promise;\n}\n\n/**\n * Take a potentially misbehaving resolver function and make sure\n * onFulfilled and onRejected are only called once.\n *\n * Makes no guarantees about asynchrony.\n */\nfunction doResolve(fn, self) {\n var done = false;\n try {\n fn(\n function(value) {\n if (done) return;\n done = true;\n resolve(self, value);\n },\n function(reason) {\n if (done) return;\n done = true;\n reject(self, reason);\n }\n );\n } catch (ex) {\n if (done) return;\n done = true;\n reject(self, ex);\n }\n}\n\nPromise.prototype['catch'] = function(onRejected) {\n return this.then(null, onRejected);\n};\n\nPromise.prototype.then = function(onFulfilled, onRejected) {\n // @ts-ignore\n var prom = new this.constructor(noop);\n\n handle(this, new Handler(onFulfilled, onRejected, prom));\n return prom;\n};\n\nPromise.prototype['finally'] = promiseFinally;\n\nPromise.all = function(arr) {\n return new Promise(function(resolve, reject) {\n if (!isArray(arr)) {\n return reject(new TypeError('Promise.all accepts an array'));\n }\n\n var args = Array.prototype.slice.call(arr);\n if (args.length === 0) return resolve([]);\n var remaining = args.length;\n\n function res(i, val) {\n try {\n if (val && (typeof val === 'object' || typeof val === 'function')) {\n var then = val.then;\n if (typeof then === 'function') {\n then.call(\n val,\n function(val) {\n res(i, val);\n },\n reject\n );\n return;\n }\n }\n args[i] = val;\n if (--remaining === 0) {\n resolve(args);\n }\n } catch (ex) {\n reject(ex);\n }\n }\n\n for (var i = 0; i < args.length; i++) {\n res(i, args[i]);\n }\n });\n};\n\nPromise.any = any;\n\nPromise.allSettled = allSettled;\n\nPromise.resolve = function(value) {\n if (value && typeof value === 'object' && value.constructor === Promise) {\n return value;\n }\n\n return new Promise(function(resolve) {\n resolve(value);\n });\n};\n\nPromise.reject = function(value) {\n return new Promise(function(resolve, reject) {\n reject(value);\n });\n};\n\nPromise.race = function(arr) {\n return new Promise(function(resolve, reject) {\n if (!isArray(arr)) {\n return reject(new TypeError('Promise.race accepts an array'));\n }\n\n for (var i = 0, len = arr.length; i < len; i++) {\n Promise.resolve(arr[i]).then(resolve, reject);\n }\n });\n};\n\n// Use polyfill for setImmediate for performance gains\nPromise._immediateFn =\n // @ts-ignore\n (typeof setImmediate === 'function' &&\n function(fn) {\n // @ts-ignore\n setImmediate(fn);\n }) ||\n function(fn) {\n setTimeoutFunc(fn, 0);\n };\n\nPromise._unhandledRejectionFn = function _unhandledRejectionFn(err) {\n if (typeof console !== 'undefined' && console) {\n console.warn('Possible Unhandled Promise Rejection:', err); // eslint-disable-line no-console\n }\n};\n\nexport default Promise;\n", null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, "/*\nTurbo 7.3.0\nCopyright \u00A9 2023 37signals LLC\n */\n(function () {\n if (window.Reflect === undefined ||\n window.customElements === undefined ||\n window.customElements.polyfillWrapFlushCallback) {\n return;\n }\n const BuiltInHTMLElement = HTMLElement;\n const wrapperForTheName = {\n HTMLElement: function HTMLElement() {\n return Reflect.construct(BuiltInHTMLElement, [], this.constructor);\n },\n };\n window.HTMLElement = wrapperForTheName[\"HTMLElement\"];\n HTMLElement.prototype = BuiltInHTMLElement.prototype;\n HTMLElement.prototype.constructor = HTMLElement;\n Object.setPrototypeOf(HTMLElement, BuiltInHTMLElement);\n})();\n\n/**\n * The MIT License (MIT)\n * \n * Copyright (c) 2019 Javan Makhmali\n * \n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n * \n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n * \n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n(function(prototype) {\n if (typeof prototype.requestSubmit == \"function\") return\n\n prototype.requestSubmit = function(submitter) {\n if (submitter) {\n validateSubmitter(submitter, this);\n submitter.click();\n } else {\n submitter = document.createElement(\"input\");\n submitter.type = \"submit\";\n submitter.hidden = true;\n this.appendChild(submitter);\n submitter.click();\n this.removeChild(submitter);\n }\n };\n\n function validateSubmitter(submitter, form) {\n submitter instanceof HTMLElement || raise(TypeError, \"parameter 1 is not of type 'HTMLElement'\");\n submitter.type == \"submit\" || raise(TypeError, \"The specified element is not a submit button\");\n submitter.form == form || raise(DOMException, \"The specified element is not owned by this form element\", \"NotFoundError\");\n }\n\n function raise(errorConstructor, message, name) {\n throw new errorConstructor(\"Failed to execute 'requestSubmit' on 'HTMLFormElement': \" + message + \".\", name)\n }\n})(HTMLFormElement.prototype);\n\nconst submittersByForm = new WeakMap();\nfunction findSubmitterFromClickTarget(target) {\n const element = target instanceof Element ? target : target instanceof Node ? target.parentElement : null;\n const candidate = element ? element.closest(\"input, button\") : null;\n return (candidate === null || candidate === void 0 ? void 0 : candidate.type) == \"submit\" ? candidate : null;\n}\nfunction clickCaptured(event) {\n const submitter = findSubmitterFromClickTarget(event.target);\n if (submitter && submitter.form) {\n submittersByForm.set(submitter.form, submitter);\n }\n}\n(function () {\n if (\"submitter\" in Event.prototype)\n return;\n let prototype = window.Event.prototype;\n if (\"SubmitEvent\" in window && /Apple Computer/.test(navigator.vendor)) {\n prototype = window.SubmitEvent.prototype;\n }\n else if (\"SubmitEvent\" in window) {\n return;\n }\n addEventListener(\"click\", clickCaptured, true);\n Object.defineProperty(prototype, \"submitter\", {\n get() {\n if (this.type == \"submit\" && this.target instanceof HTMLFormElement) {\n return submittersByForm.get(this.target);\n }\n },\n });\n})();\n\nvar FrameLoadingStyle;\n(function (FrameLoadingStyle) {\n FrameLoadingStyle[\"eager\"] = \"eager\";\n FrameLoadingStyle[\"lazy\"] = \"lazy\";\n})(FrameLoadingStyle || (FrameLoadingStyle = {}));\nclass FrameElement extends HTMLElement {\n static get observedAttributes() {\n return [\"disabled\", \"complete\", \"loading\", \"src\"];\n }\n constructor() {\n super();\n this.loaded = Promise.resolve();\n this.delegate = new FrameElement.delegateConstructor(this);\n }\n connectedCallback() {\n this.delegate.connect();\n }\n disconnectedCallback() {\n this.delegate.disconnect();\n }\n reload() {\n return this.delegate.sourceURLReloaded();\n }\n attributeChangedCallback(name) {\n if (name == \"loading\") {\n this.delegate.loadingStyleChanged();\n }\n else if (name == \"complete\") {\n this.delegate.completeChanged();\n }\n else if (name == \"src\") {\n this.delegate.sourceURLChanged();\n }\n else {\n this.delegate.disabledChanged();\n }\n }\n get src() {\n return this.getAttribute(\"src\");\n }\n set src(value) {\n if (value) {\n this.setAttribute(\"src\", value);\n }\n else {\n this.removeAttribute(\"src\");\n }\n }\n get loading() {\n return frameLoadingStyleFromString(this.getAttribute(\"loading\") || \"\");\n }\n set loading(value) {\n if (value) {\n this.setAttribute(\"loading\", value);\n }\n else {\n this.removeAttribute(\"loading\");\n }\n }\n get disabled() {\n return this.hasAttribute(\"disabled\");\n }\n set disabled(value) {\n if (value) {\n this.setAttribute(\"disabled\", \"\");\n }\n else {\n this.removeAttribute(\"disabled\");\n }\n }\n get autoscroll() {\n return this.hasAttribute(\"autoscroll\");\n }\n set autoscroll(value) {\n if (value) {\n this.setAttribute(\"autoscroll\", \"\");\n }\n else {\n this.removeAttribute(\"autoscroll\");\n }\n }\n get complete() {\n return !this.delegate.isLoading;\n }\n get isActive() {\n return this.ownerDocument === document && !this.isPreview;\n }\n get isPreview() {\n var _a, _b;\n return (_b = (_a = this.ownerDocument) === null || _a === void 0 ? void 0 : _a.documentElement) === null || _b === void 0 ? void 0 : _b.hasAttribute(\"data-turbo-preview\");\n }\n}\nfunction frameLoadingStyleFromString(style) {\n switch (style.toLowerCase()) {\n case \"lazy\":\n return FrameLoadingStyle.lazy;\n default:\n return FrameLoadingStyle.eager;\n }\n}\n\nfunction expandURL(locatable) {\n return new URL(locatable.toString(), document.baseURI);\n}\nfunction getAnchor(url) {\n let anchorMatch;\n if (url.hash) {\n return url.hash.slice(1);\n }\n else if ((anchorMatch = url.href.match(/#(.*)$/))) {\n return anchorMatch[1];\n }\n}\nfunction getAction(form, submitter) {\n const action = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute(\"formaction\")) || form.getAttribute(\"action\") || form.action;\n return expandURL(action);\n}\nfunction getExtension(url) {\n return (getLastPathComponent(url).match(/\\.[^.]*$/) || [])[0] || \"\";\n}\nfunction isHTML(url) {\n return !!getExtension(url).match(/^(?:|\\.(?:htm|html|xhtml|php))$/);\n}\nfunction isPrefixedBy(baseURL, url) {\n const prefix = getPrefix(url);\n return baseURL.href === expandURL(prefix).href || baseURL.href.startsWith(prefix);\n}\nfunction locationIsVisitable(location, rootLocation) {\n return isPrefixedBy(location, rootLocation) && isHTML(location);\n}\nfunction getRequestURL(url) {\n const anchor = getAnchor(url);\n return anchor != null ? url.href.slice(0, -(anchor.length + 1)) : url.href;\n}\nfunction toCacheKey(url) {\n return getRequestURL(url);\n}\nfunction urlsAreEqual(left, right) {\n return expandURL(left).href == expandURL(right).href;\n}\nfunction getPathComponents(url) {\n return url.pathname.split(\"/\").slice(1);\n}\nfunction getLastPathComponent(url) {\n return getPathComponents(url).slice(-1)[0];\n}\nfunction getPrefix(url) {\n return addTrailingSlash(url.origin + url.pathname);\n}\nfunction addTrailingSlash(value) {\n return value.endsWith(\"/\") ? value : value + \"/\";\n}\n\nclass FetchResponse {\n constructor(response) {\n this.response = response;\n }\n get succeeded() {\n return this.response.ok;\n }\n get failed() {\n return !this.succeeded;\n }\n get clientError() {\n return this.statusCode >= 400 && this.statusCode <= 499;\n }\n get serverError() {\n return this.statusCode >= 500 && this.statusCode <= 599;\n }\n get redirected() {\n return this.response.redirected;\n }\n get location() {\n return expandURL(this.response.url);\n }\n get isHTML() {\n return this.contentType && this.contentType.match(/^(?:text\\/([^\\s;,]+\\b)?html|application\\/xhtml\\+xml)\\b/);\n }\n get statusCode() {\n return this.response.status;\n }\n get contentType() {\n return this.header(\"Content-Type\");\n }\n get responseText() {\n return this.response.clone().text();\n }\n get responseHTML() {\n if (this.isHTML) {\n return this.response.clone().text();\n }\n else {\n return Promise.resolve(undefined);\n }\n }\n header(name) {\n return this.response.headers.get(name);\n }\n}\n\nfunction activateScriptElement(element) {\n if (element.getAttribute(\"data-turbo-eval\") == \"false\") {\n return element;\n }\n else {\n const createdScriptElement = document.createElement(\"script\");\n const cspNonce = getMetaContent(\"csp-nonce\");\n if (cspNonce) {\n createdScriptElement.nonce = cspNonce;\n }\n createdScriptElement.textContent = element.textContent;\n createdScriptElement.async = false;\n copyElementAttributes(createdScriptElement, element);\n return createdScriptElement;\n }\n}\nfunction copyElementAttributes(destinationElement, sourceElement) {\n for (const { name, value } of sourceElement.attributes) {\n destinationElement.setAttribute(name, value);\n }\n}\nfunction createDocumentFragment(html) {\n const template = document.createElement(\"template\");\n template.innerHTML = html;\n return template.content;\n}\nfunction dispatch(eventName, { target, cancelable, detail } = {}) {\n const event = new CustomEvent(eventName, {\n cancelable,\n bubbles: true,\n composed: true,\n detail,\n });\n if (target && target.isConnected) {\n target.dispatchEvent(event);\n }\n else {\n document.documentElement.dispatchEvent(event);\n }\n return event;\n}\nfunction nextAnimationFrame() {\n return new Promise((resolve) => requestAnimationFrame(() => resolve()));\n}\nfunction nextEventLoopTick() {\n return new Promise((resolve) => setTimeout(() => resolve(), 0));\n}\nfunction nextMicrotask() {\n return Promise.resolve();\n}\nfunction parseHTMLDocument(html = \"\") {\n return new DOMParser().parseFromString(html, \"text/html\");\n}\nfunction unindent(strings, ...values) {\n const lines = interpolate(strings, values).replace(/^\\n/, \"\").split(\"\\n\");\n const match = lines[0].match(/^\\s+/);\n const indent = match ? match[0].length : 0;\n return lines.map((line) => line.slice(indent)).join(\"\\n\");\n}\nfunction interpolate(strings, values) {\n return strings.reduce((result, string, i) => {\n const value = values[i] == undefined ? \"\" : values[i];\n return result + string + value;\n }, \"\");\n}\nfunction uuid() {\n return Array.from({ length: 36 })\n .map((_, i) => {\n if (i == 8 || i == 13 || i == 18 || i == 23) {\n return \"-\";\n }\n else if (i == 14) {\n return \"4\";\n }\n else if (i == 19) {\n return (Math.floor(Math.random() * 4) + 8).toString(16);\n }\n else {\n return Math.floor(Math.random() * 15).toString(16);\n }\n })\n .join(\"\");\n}\nfunction getAttribute(attributeName, ...elements) {\n for (const value of elements.map((element) => element === null || element === void 0 ? void 0 : element.getAttribute(attributeName))) {\n if (typeof value == \"string\")\n return value;\n }\n return null;\n}\nfunction hasAttribute(attributeName, ...elements) {\n return elements.some((element) => element && element.hasAttribute(attributeName));\n}\nfunction markAsBusy(...elements) {\n for (const element of elements) {\n if (element.localName == \"turbo-frame\") {\n element.setAttribute(\"busy\", \"\");\n }\n element.setAttribute(\"aria-busy\", \"true\");\n }\n}\nfunction clearBusyState(...elements) {\n for (const element of elements) {\n if (element.localName == \"turbo-frame\") {\n element.removeAttribute(\"busy\");\n }\n element.removeAttribute(\"aria-busy\");\n }\n}\nfunction waitForLoad(element, timeoutInMilliseconds = 2000) {\n return new Promise((resolve) => {\n const onComplete = () => {\n element.removeEventListener(\"error\", onComplete);\n element.removeEventListener(\"load\", onComplete);\n resolve();\n };\n element.addEventListener(\"load\", onComplete, { once: true });\n element.addEventListener(\"error\", onComplete, { once: true });\n setTimeout(resolve, timeoutInMilliseconds);\n });\n}\nfunction getHistoryMethodForAction(action) {\n switch (action) {\n case \"replace\":\n return history.replaceState;\n case \"advance\":\n case \"restore\":\n return history.pushState;\n }\n}\nfunction isAction(action) {\n return action == \"advance\" || action == \"replace\" || action == \"restore\";\n}\nfunction getVisitAction(...elements) {\n const action = getAttribute(\"data-turbo-action\", ...elements);\n return isAction(action) ? action : null;\n}\nfunction getMetaElement(name) {\n return document.querySelector(`meta[name=\"${name}\"]`);\n}\nfunction getMetaContent(name) {\n const element = getMetaElement(name);\n return element && element.content;\n}\nfunction setMetaContent(name, content) {\n let element = getMetaElement(name);\n if (!element) {\n element = document.createElement(\"meta\");\n element.setAttribute(\"name\", name);\n document.head.appendChild(element);\n }\n element.setAttribute(\"content\", content);\n return element;\n}\nfunction findClosestRecursively(element, selector) {\n var _a;\n if (element instanceof Element) {\n return (element.closest(selector) ||\n findClosestRecursively(element.assignedSlot || ((_a = element.getRootNode()) === null || _a === void 0 ? void 0 : _a.host), selector));\n }\n}\n\nvar FetchMethod;\n(function (FetchMethod) {\n FetchMethod[FetchMethod[\"get\"] = 0] = \"get\";\n FetchMethod[FetchMethod[\"post\"] = 1] = \"post\";\n FetchMethod[FetchMethod[\"put\"] = 2] = \"put\";\n FetchMethod[FetchMethod[\"patch\"] = 3] = \"patch\";\n FetchMethod[FetchMethod[\"delete\"] = 4] = \"delete\";\n})(FetchMethod || (FetchMethod = {}));\nfunction fetchMethodFromString(method) {\n switch (method.toLowerCase()) {\n case \"get\":\n return FetchMethod.get;\n case \"post\":\n return FetchMethod.post;\n case \"put\":\n return FetchMethod.put;\n case \"patch\":\n return FetchMethod.patch;\n case \"delete\":\n return FetchMethod.delete;\n }\n}\nclass FetchRequest {\n constructor(delegate, method, location, body = new URLSearchParams(), target = null) {\n this.abortController = new AbortController();\n this.resolveRequestPromise = (_value) => { };\n this.delegate = delegate;\n this.method = method;\n this.headers = this.defaultHeaders;\n this.body = body;\n this.url = location;\n this.target = target;\n }\n get location() {\n return this.url;\n }\n get params() {\n return this.url.searchParams;\n }\n get entries() {\n return this.body ? Array.from(this.body.entries()) : [];\n }\n cancel() {\n this.abortController.abort();\n }\n async perform() {\n const { fetchOptions } = this;\n this.delegate.prepareRequest(this);\n await this.allowRequestToBeIntercepted(fetchOptions);\n try {\n this.delegate.requestStarted(this);\n const response = await fetch(this.url.href, fetchOptions);\n return await this.receive(response);\n }\n catch (error) {\n if (error.name !== \"AbortError\") {\n if (this.willDelegateErrorHandling(error)) {\n this.delegate.requestErrored(this, error);\n }\n throw error;\n }\n }\n finally {\n this.delegate.requestFinished(this);\n }\n }\n async receive(response) {\n const fetchResponse = new FetchResponse(response);\n const event = dispatch(\"turbo:before-fetch-response\", {\n cancelable: true,\n detail: { fetchResponse },\n target: this.target,\n });\n if (event.defaultPrevented) {\n this.delegate.requestPreventedHandlingResponse(this, fetchResponse);\n }\n else if (fetchResponse.succeeded) {\n this.delegate.requestSucceededWithResponse(this, fetchResponse);\n }\n else {\n this.delegate.requestFailedWithResponse(this, fetchResponse);\n }\n return fetchResponse;\n }\n get fetchOptions() {\n var _a;\n return {\n method: FetchMethod[this.method].toUpperCase(),\n credentials: \"same-origin\",\n headers: this.headers,\n redirect: \"follow\",\n body: this.isSafe ? null : this.body,\n signal: this.abortSignal,\n referrer: (_a = this.delegate.referrer) === null || _a === void 0 ? void 0 : _a.href,\n };\n }\n get defaultHeaders() {\n return {\n Accept: \"text/html, application/xhtml+xml\",\n };\n }\n get isSafe() {\n return this.method === FetchMethod.get;\n }\n get abortSignal() {\n return this.abortController.signal;\n }\n acceptResponseType(mimeType) {\n this.headers[\"Accept\"] = [mimeType, this.headers[\"Accept\"]].join(\", \");\n }\n async allowRequestToBeIntercepted(fetchOptions) {\n const requestInterception = new Promise((resolve) => (this.resolveRequestPromise = resolve));\n const event = dispatch(\"turbo:before-fetch-request\", {\n cancelable: true,\n detail: {\n fetchOptions,\n url: this.url,\n resume: this.resolveRequestPromise,\n },\n target: this.target,\n });\n if (event.defaultPrevented)\n await requestInterception;\n }\n willDelegateErrorHandling(error) {\n const event = dispatch(\"turbo:fetch-request-error\", {\n target: this.target,\n cancelable: true,\n detail: { request: this, error: error },\n });\n return !event.defaultPrevented;\n }\n}\n\nclass AppearanceObserver {\n constructor(delegate, element) {\n this.started = false;\n this.intersect = (entries) => {\n const lastEntry = entries.slice(-1)[0];\n if (lastEntry === null || lastEntry === void 0 ? void 0 : lastEntry.isIntersecting) {\n this.delegate.elementAppearedInViewport(this.element);\n }\n };\n this.delegate = delegate;\n this.element = element;\n this.intersectionObserver = new IntersectionObserver(this.intersect);\n }\n start() {\n if (!this.started) {\n this.started = true;\n this.intersectionObserver.observe(this.element);\n }\n }\n stop() {\n if (this.started) {\n this.started = false;\n this.intersectionObserver.unobserve(this.element);\n }\n }\n}\n\nclass StreamMessage {\n static wrap(message) {\n if (typeof message == \"string\") {\n return new this(createDocumentFragment(message));\n }\n else {\n return message;\n }\n }\n constructor(fragment) {\n this.fragment = importStreamElements(fragment);\n }\n}\nStreamMessage.contentType = \"text/vnd.turbo-stream.html\";\nfunction importStreamElements(fragment) {\n for (const element of fragment.querySelectorAll(\"turbo-stream\")) {\n const streamElement = document.importNode(element, true);\n for (const inertScriptElement of streamElement.templateElement.content.querySelectorAll(\"script\")) {\n inertScriptElement.replaceWith(activateScriptElement(inertScriptElement));\n }\n element.replaceWith(streamElement);\n }\n return fragment;\n}\n\nvar FormSubmissionState;\n(function (FormSubmissionState) {\n FormSubmissionState[FormSubmissionState[\"initialized\"] = 0] = \"initialized\";\n FormSubmissionState[FormSubmissionState[\"requesting\"] = 1] = \"requesting\";\n FormSubmissionState[FormSubmissionState[\"waiting\"] = 2] = \"waiting\";\n FormSubmissionState[FormSubmissionState[\"receiving\"] = 3] = \"receiving\";\n FormSubmissionState[FormSubmissionState[\"stopping\"] = 4] = \"stopping\";\n FormSubmissionState[FormSubmissionState[\"stopped\"] = 5] = \"stopped\";\n})(FormSubmissionState || (FormSubmissionState = {}));\nvar FormEnctype;\n(function (FormEnctype) {\n FormEnctype[\"urlEncoded\"] = \"application/x-www-form-urlencoded\";\n FormEnctype[\"multipart\"] = \"multipart/form-data\";\n FormEnctype[\"plain\"] = \"text/plain\";\n})(FormEnctype || (FormEnctype = {}));\nfunction formEnctypeFromString(encoding) {\n switch (encoding.toLowerCase()) {\n case FormEnctype.multipart:\n return FormEnctype.multipart;\n case FormEnctype.plain:\n return FormEnctype.plain;\n default:\n return FormEnctype.urlEncoded;\n }\n}\nclass FormSubmission {\n static confirmMethod(message, _element, _submitter) {\n return Promise.resolve(confirm(message));\n }\n constructor(delegate, formElement, submitter, mustRedirect = false) {\n this.state = FormSubmissionState.initialized;\n this.delegate = delegate;\n this.formElement = formElement;\n this.submitter = submitter;\n this.formData = buildFormData(formElement, submitter);\n this.location = expandURL(this.action);\n if (this.method == FetchMethod.get) {\n mergeFormDataEntries(this.location, [...this.body.entries()]);\n }\n this.fetchRequest = new FetchRequest(this, this.method, this.location, this.body, this.formElement);\n this.mustRedirect = mustRedirect;\n }\n get method() {\n var _a;\n const method = ((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute(\"formmethod\")) || this.formElement.getAttribute(\"method\") || \"\";\n return fetchMethodFromString(method.toLowerCase()) || FetchMethod.get;\n }\n get action() {\n var _a;\n const formElementAction = typeof this.formElement.action === \"string\" ? this.formElement.action : null;\n if ((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.hasAttribute(\"formaction\")) {\n return this.submitter.getAttribute(\"formaction\") || \"\";\n }\n else {\n return this.formElement.getAttribute(\"action\") || formElementAction || \"\";\n }\n }\n get body() {\n if (this.enctype == FormEnctype.urlEncoded || this.method == FetchMethod.get) {\n return new URLSearchParams(this.stringFormData);\n }\n else {\n return this.formData;\n }\n }\n get enctype() {\n var _a;\n return formEnctypeFromString(((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute(\"formenctype\")) || this.formElement.enctype);\n }\n get isSafe() {\n return this.fetchRequest.isSafe;\n }\n get stringFormData() {\n return [...this.formData].reduce((entries, [name, value]) => {\n return entries.concat(typeof value == \"string\" ? [[name, value]] : []);\n }, []);\n }\n async start() {\n const { initialized, requesting } = FormSubmissionState;\n const confirmationMessage = getAttribute(\"data-turbo-confirm\", this.submitter, this.formElement);\n if (typeof confirmationMessage === \"string\") {\n const answer = await FormSubmission.confirmMethod(confirmationMessage, this.formElement, this.submitter);\n if (!answer) {\n return;\n }\n }\n if (this.state == initialized) {\n this.state = requesting;\n return this.fetchRequest.perform();\n }\n }\n stop() {\n const { stopping, stopped } = FormSubmissionState;\n if (this.state != stopping && this.state != stopped) {\n this.state = stopping;\n this.fetchRequest.cancel();\n return true;\n }\n }\n prepareRequest(request) {\n if (!request.isSafe) {\n const token = getCookieValue(getMetaContent(\"csrf-param\")) || getMetaContent(\"csrf-token\");\n if (token) {\n request.headers[\"X-CSRF-Token\"] = token;\n }\n }\n if (this.requestAcceptsTurboStreamResponse(request)) {\n request.acceptResponseType(StreamMessage.contentType);\n }\n }\n requestStarted(_request) {\n var _a;\n this.state = FormSubmissionState.waiting;\n (_a = this.submitter) === null || _a === void 0 ? void 0 : _a.setAttribute(\"disabled\", \"\");\n this.setSubmitsWith();\n dispatch(\"turbo:submit-start\", {\n target: this.formElement,\n detail: { formSubmission: this },\n });\n this.delegate.formSubmissionStarted(this);\n }\n requestPreventedHandlingResponse(request, response) {\n this.result = { success: response.succeeded, fetchResponse: response };\n }\n requestSucceededWithResponse(request, response) {\n if (response.clientError || response.serverError) {\n this.delegate.formSubmissionFailedWithResponse(this, response);\n }\n else if (this.requestMustRedirect(request) && responseSucceededWithoutRedirect(response)) {\n const error = new Error(\"Form responses must redirect to another location\");\n this.delegate.formSubmissionErrored(this, error);\n }\n else {\n this.state = FormSubmissionState.receiving;\n this.result = { success: true, fetchResponse: response };\n this.delegate.formSubmissionSucceededWithResponse(this, response);\n }\n }\n requestFailedWithResponse(request, response) {\n this.result = { success: false, fetchResponse: response };\n this.delegate.formSubmissionFailedWithResponse(this, response);\n }\n requestErrored(request, error) {\n this.result = { success: false, error };\n this.delegate.formSubmissionErrored(this, error);\n }\n requestFinished(_request) {\n var _a;\n this.state = FormSubmissionState.stopped;\n (_a = this.submitter) === null || _a === void 0 ? void 0 : _a.removeAttribute(\"disabled\");\n this.resetSubmitterText();\n dispatch(\"turbo:submit-end\", {\n target: this.formElement,\n detail: Object.assign({ formSubmission: this }, this.result),\n });\n this.delegate.formSubmissionFinished(this);\n }\n setSubmitsWith() {\n if (!this.submitter || !this.submitsWith)\n return;\n if (this.submitter.matches(\"button\")) {\n this.originalSubmitText = this.submitter.innerHTML;\n this.submitter.innerHTML = this.submitsWith;\n }\n else if (this.submitter.matches(\"input\")) {\n const input = this.submitter;\n this.originalSubmitText = input.value;\n input.value = this.submitsWith;\n }\n }\n resetSubmitterText() {\n if (!this.submitter || !this.originalSubmitText)\n return;\n if (this.submitter.matches(\"button\")) {\n this.submitter.innerHTML = this.originalSubmitText;\n }\n else if (this.submitter.matches(\"input\")) {\n const input = this.submitter;\n input.value = this.originalSubmitText;\n }\n }\n requestMustRedirect(request) {\n return !request.isSafe && this.mustRedirect;\n }\n requestAcceptsTurboStreamResponse(request) {\n return !request.isSafe || hasAttribute(\"data-turbo-stream\", this.submitter, this.formElement);\n }\n get submitsWith() {\n var _a;\n return (_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute(\"data-turbo-submits-with\");\n }\n}\nfunction buildFormData(formElement, submitter) {\n const formData = new FormData(formElement);\n const name = submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute(\"name\");\n const value = submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute(\"value\");\n if (name) {\n formData.append(name, value || \"\");\n }\n return formData;\n}\nfunction getCookieValue(cookieName) {\n if (cookieName != null) {\n const cookies = document.cookie ? document.cookie.split(\"; \") : [];\n const cookie = cookies.find((cookie) => cookie.startsWith(cookieName));\n if (cookie) {\n const value = cookie.split(\"=\").slice(1).join(\"=\");\n return value ? decodeURIComponent(value) : undefined;\n }\n }\n}\nfunction responseSucceededWithoutRedirect(response) {\n return response.statusCode == 200 && !response.redirected;\n}\nfunction mergeFormDataEntries(url, entries) {\n const searchParams = new URLSearchParams();\n for (const [name, value] of entries) {\n if (value instanceof File)\n continue;\n searchParams.append(name, value);\n }\n url.search = searchParams.toString();\n return url;\n}\n\nclass Snapshot {\n constructor(element) {\n this.element = element;\n }\n get activeElement() {\n return this.element.ownerDocument.activeElement;\n }\n get children() {\n return [...this.element.children];\n }\n hasAnchor(anchor) {\n return this.getElementForAnchor(anchor) != null;\n }\n getElementForAnchor(anchor) {\n return anchor ? this.element.querySelector(`[id='${anchor}'], a[name='${anchor}']`) : null;\n }\n get isConnected() {\n return this.element.isConnected;\n }\n get firstAutofocusableElement() {\n const inertDisabledOrHidden = \"[inert], :disabled, [hidden], details:not([open]), dialog:not([open])\";\n for (const element of this.element.querySelectorAll(\"[autofocus]\")) {\n if (element.closest(inertDisabledOrHidden) == null)\n return element;\n else\n continue;\n }\n return null;\n }\n get permanentElements() {\n return queryPermanentElementsAll(this.element);\n }\n getPermanentElementById(id) {\n return getPermanentElementById(this.element, id);\n }\n getPermanentElementMapForSnapshot(snapshot) {\n const permanentElementMap = {};\n for (const currentPermanentElement of this.permanentElements) {\n const { id } = currentPermanentElement;\n const newPermanentElement = snapshot.getPermanentElementById(id);\n if (newPermanentElement) {\n permanentElementMap[id] = [currentPermanentElement, newPermanentElement];\n }\n }\n return permanentElementMap;\n }\n}\nfunction getPermanentElementById(node, id) {\n return node.querySelector(`#${id}[data-turbo-permanent]`);\n}\nfunction queryPermanentElementsAll(node) {\n return node.querySelectorAll(\"[id][data-turbo-permanent]\");\n}\n\nclass FormSubmitObserver {\n constructor(delegate, eventTarget) {\n this.started = false;\n this.submitCaptured = () => {\n this.eventTarget.removeEventListener(\"submit\", this.submitBubbled, false);\n this.eventTarget.addEventListener(\"submit\", this.submitBubbled, false);\n };\n this.submitBubbled = ((event) => {\n if (!event.defaultPrevented) {\n const form = event.target instanceof HTMLFormElement ? event.target : undefined;\n const submitter = event.submitter || undefined;\n if (form &&\n submissionDoesNotDismissDialog(form, submitter) &&\n submissionDoesNotTargetIFrame(form, submitter) &&\n this.delegate.willSubmitForm(form, submitter)) {\n event.preventDefault();\n event.stopImmediatePropagation();\n this.delegate.formSubmitted(form, submitter);\n }\n }\n });\n this.delegate = delegate;\n this.eventTarget = eventTarget;\n }\n start() {\n if (!this.started) {\n this.eventTarget.addEventListener(\"submit\", this.submitCaptured, true);\n this.started = true;\n }\n }\n stop() {\n if (this.started) {\n this.eventTarget.removeEventListener(\"submit\", this.submitCaptured, true);\n this.started = false;\n }\n }\n}\nfunction submissionDoesNotDismissDialog(form, submitter) {\n const method = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute(\"formmethod\")) || form.getAttribute(\"method\");\n return method != \"dialog\";\n}\nfunction submissionDoesNotTargetIFrame(form, submitter) {\n if ((submitter === null || submitter === void 0 ? void 0 : submitter.hasAttribute(\"formtarget\")) || form.hasAttribute(\"target\")) {\n const target = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute(\"formtarget\")) || form.target;\n for (const element of document.getElementsByName(target)) {\n if (element instanceof HTMLIFrameElement)\n return false;\n }\n return true;\n }\n else {\n return true;\n }\n}\n\nclass View {\n constructor(delegate, element) {\n this.resolveRenderPromise = (_value) => { };\n this.resolveInterceptionPromise = (_value) => { };\n this.delegate = delegate;\n this.element = element;\n }\n scrollToAnchor(anchor) {\n const element = this.snapshot.getElementForAnchor(anchor);\n if (element) {\n this.scrollToElement(element);\n this.focusElement(element);\n }\n else {\n this.scrollToPosition({ x: 0, y: 0 });\n }\n }\n scrollToAnchorFromLocation(location) {\n this.scrollToAnchor(getAnchor(location));\n }\n scrollToElement(element) {\n element.scrollIntoView();\n }\n focusElement(element) {\n if (element instanceof HTMLElement) {\n if (element.hasAttribute(\"tabindex\")) {\n element.focus();\n }\n else {\n element.setAttribute(\"tabindex\", \"-1\");\n element.focus();\n element.removeAttribute(\"tabindex\");\n }\n }\n }\n scrollToPosition({ x, y }) {\n this.scrollRoot.scrollTo(x, y);\n }\n scrollToTop() {\n this.scrollToPosition({ x: 0, y: 0 });\n }\n get scrollRoot() {\n return window;\n }\n async render(renderer) {\n const { isPreview, shouldRender, newSnapshot: snapshot } = renderer;\n if (shouldRender) {\n try {\n this.renderPromise = new Promise((resolve) => (this.resolveRenderPromise = resolve));\n this.renderer = renderer;\n await this.prepareToRenderSnapshot(renderer);\n const renderInterception = new Promise((resolve) => (this.resolveInterceptionPromise = resolve));\n const options = { resume: this.resolveInterceptionPromise, render: this.renderer.renderElement };\n const immediateRender = this.delegate.allowsImmediateRender(snapshot, options);\n if (!immediateRender)\n await renderInterception;\n await this.renderSnapshot(renderer);\n this.delegate.viewRenderedSnapshot(snapshot, isPreview);\n this.delegate.preloadOnLoadLinksForView(this.element);\n this.finishRenderingSnapshot(renderer);\n }\n finally {\n delete this.renderer;\n this.resolveRenderPromise(undefined);\n delete this.renderPromise;\n }\n }\n else {\n this.invalidate(renderer.reloadReason);\n }\n }\n invalidate(reason) {\n this.delegate.viewInvalidated(reason);\n }\n async prepareToRenderSnapshot(renderer) {\n this.markAsPreview(renderer.isPreview);\n await renderer.prepareToRender();\n }\n markAsPreview(isPreview) {\n if (isPreview) {\n this.element.setAttribute(\"data-turbo-preview\", \"\");\n }\n else {\n this.element.removeAttribute(\"data-turbo-preview\");\n }\n }\n async renderSnapshot(renderer) {\n await renderer.render();\n }\n finishRenderingSnapshot(renderer) {\n renderer.finishRendering();\n }\n}\n\nclass FrameView extends View {\n missing() {\n this.element.innerHTML = `Content missing`;\n }\n get snapshot() {\n return new Snapshot(this.element);\n }\n}\n\nclass LinkInterceptor {\n constructor(delegate, element) {\n this.clickBubbled = (event) => {\n if (this.respondsToEventTarget(event.target)) {\n this.clickEvent = event;\n }\n else {\n delete this.clickEvent;\n }\n };\n this.linkClicked = ((event) => {\n if (this.clickEvent && this.respondsToEventTarget(event.target) && event.target instanceof Element) {\n if (this.delegate.shouldInterceptLinkClick(event.target, event.detail.url, event.detail.originalEvent)) {\n this.clickEvent.preventDefault();\n event.preventDefault();\n this.delegate.linkClickIntercepted(event.target, event.detail.url, event.detail.originalEvent);\n }\n }\n delete this.clickEvent;\n });\n this.willVisit = ((_event) => {\n delete this.clickEvent;\n });\n this.delegate = delegate;\n this.element = element;\n }\n start() {\n this.element.addEventListener(\"click\", this.clickBubbled);\n document.addEventListener(\"turbo:click\", this.linkClicked);\n document.addEventListener(\"turbo:before-visit\", this.willVisit);\n }\n stop() {\n this.element.removeEventListener(\"click\", this.clickBubbled);\n document.removeEventListener(\"turbo:click\", this.linkClicked);\n document.removeEventListener(\"turbo:before-visit\", this.willVisit);\n }\n respondsToEventTarget(target) {\n const element = target instanceof Element ? target : target instanceof Node ? target.parentElement : null;\n return element && element.closest(\"turbo-frame, html\") == this.element;\n }\n}\n\nclass LinkClickObserver {\n constructor(delegate, eventTarget) {\n this.started = false;\n this.clickCaptured = () => {\n this.eventTarget.removeEventListener(\"click\", this.clickBubbled, false);\n this.eventTarget.addEventListener(\"click\", this.clickBubbled, false);\n };\n this.clickBubbled = (event) => {\n if (event instanceof MouseEvent && this.clickEventIsSignificant(event)) {\n const target = (event.composedPath && event.composedPath()[0]) || event.target;\n const link = this.findLinkFromClickTarget(target);\n if (link && doesNotTargetIFrame(link)) {\n const location = this.getLocationForLink(link);\n if (this.delegate.willFollowLinkToLocation(link, location, event)) {\n event.preventDefault();\n this.delegate.followedLinkToLocation(link, location);\n }\n }\n }\n };\n this.delegate = delegate;\n this.eventTarget = eventTarget;\n }\n start() {\n if (!this.started) {\n this.eventTarget.addEventListener(\"click\", this.clickCaptured, true);\n this.started = true;\n }\n }\n stop() {\n if (this.started) {\n this.eventTarget.removeEventListener(\"click\", this.clickCaptured, true);\n this.started = false;\n }\n }\n clickEventIsSignificant(event) {\n return !((event.target && event.target.isContentEditable) ||\n event.defaultPrevented ||\n event.which > 1 ||\n event.altKey ||\n event.ctrlKey ||\n event.metaKey ||\n event.shiftKey);\n }\n findLinkFromClickTarget(target) {\n return findClosestRecursively(target, \"a[href]:not([target^=_]):not([download])\");\n }\n getLocationForLink(link) {\n return expandURL(link.getAttribute(\"href\") || \"\");\n }\n}\nfunction doesNotTargetIFrame(anchor) {\n if (anchor.hasAttribute(\"target\")) {\n for (const element of document.getElementsByName(anchor.target)) {\n if (element instanceof HTMLIFrameElement)\n return false;\n }\n return true;\n }\n else {\n return true;\n }\n}\n\nclass FormLinkClickObserver {\n constructor(delegate, element) {\n this.delegate = delegate;\n this.linkInterceptor = new LinkClickObserver(this, element);\n }\n start() {\n this.linkInterceptor.start();\n }\n stop() {\n this.linkInterceptor.stop();\n }\n willFollowLinkToLocation(link, location, originalEvent) {\n return (this.delegate.willSubmitFormLinkToLocation(link, location, originalEvent) &&\n link.hasAttribute(\"data-turbo-method\"));\n }\n followedLinkToLocation(link, location) {\n const form = document.createElement(\"form\");\n const type = \"hidden\";\n for (const [name, value] of location.searchParams) {\n form.append(Object.assign(document.createElement(\"input\"), { type, name, value }));\n }\n const action = Object.assign(location, { search: \"\" });\n form.setAttribute(\"data-turbo\", \"true\");\n form.setAttribute(\"action\", action.href);\n form.setAttribute(\"hidden\", \"\");\n const method = link.getAttribute(\"data-turbo-method\");\n if (method)\n form.setAttribute(\"method\", method);\n const turboFrame = link.getAttribute(\"data-turbo-frame\");\n if (turboFrame)\n form.setAttribute(\"data-turbo-frame\", turboFrame);\n const turboAction = getVisitAction(link);\n if (turboAction)\n form.setAttribute(\"data-turbo-action\", turboAction);\n const turboConfirm = link.getAttribute(\"data-turbo-confirm\");\n if (turboConfirm)\n form.setAttribute(\"data-turbo-confirm\", turboConfirm);\n const turboStream = link.hasAttribute(\"data-turbo-stream\");\n if (turboStream)\n form.setAttribute(\"data-turbo-stream\", \"\");\n this.delegate.submittedFormLinkToLocation(link, location, form);\n document.body.appendChild(form);\n form.addEventListener(\"turbo:submit-end\", () => form.remove(), { once: true });\n requestAnimationFrame(() => form.requestSubmit());\n }\n}\n\nclass Bardo {\n static async preservingPermanentElements(delegate, permanentElementMap, callback) {\n const bardo = new this(delegate, permanentElementMap);\n bardo.enter();\n await callback();\n bardo.leave();\n }\n constructor(delegate, permanentElementMap) {\n this.delegate = delegate;\n this.permanentElementMap = permanentElementMap;\n }\n enter() {\n for (const id in this.permanentElementMap) {\n const [currentPermanentElement, newPermanentElement] = this.permanentElementMap[id];\n this.delegate.enteringBardo(currentPermanentElement, newPermanentElement);\n this.replaceNewPermanentElementWithPlaceholder(newPermanentElement);\n }\n }\n leave() {\n for (const id in this.permanentElementMap) {\n const [currentPermanentElement] = this.permanentElementMap[id];\n this.replaceCurrentPermanentElementWithClone(currentPermanentElement);\n this.replacePlaceholderWithPermanentElement(currentPermanentElement);\n this.delegate.leavingBardo(currentPermanentElement);\n }\n }\n replaceNewPermanentElementWithPlaceholder(permanentElement) {\n const placeholder = createPlaceholderForPermanentElement(permanentElement);\n permanentElement.replaceWith(placeholder);\n }\n replaceCurrentPermanentElementWithClone(permanentElement) {\n const clone = permanentElement.cloneNode(true);\n permanentElement.replaceWith(clone);\n }\n replacePlaceholderWithPermanentElement(permanentElement) {\n const placeholder = this.getPlaceholderById(permanentElement.id);\n placeholder === null || placeholder === void 0 ? void 0 : placeholder.replaceWith(permanentElement);\n }\n getPlaceholderById(id) {\n return this.placeholders.find((element) => element.content == id);\n }\n get placeholders() {\n return [...document.querySelectorAll(\"meta[name=turbo-permanent-placeholder][content]\")];\n }\n}\nfunction createPlaceholderForPermanentElement(permanentElement) {\n const element = document.createElement(\"meta\");\n element.setAttribute(\"name\", \"turbo-permanent-placeholder\");\n element.setAttribute(\"content\", permanentElement.id);\n return element;\n}\n\nclass Renderer {\n constructor(currentSnapshot, newSnapshot, renderElement, isPreview, willRender = true) {\n this.activeElement = null;\n this.currentSnapshot = currentSnapshot;\n this.newSnapshot = newSnapshot;\n this.isPreview = isPreview;\n this.willRender = willRender;\n this.renderElement = renderElement;\n this.promise = new Promise((resolve, reject) => (this.resolvingFunctions = { resolve, reject }));\n }\n get shouldRender() {\n return true;\n }\n get reloadReason() {\n return;\n }\n prepareToRender() {\n return;\n }\n finishRendering() {\n if (this.resolvingFunctions) {\n this.resolvingFunctions.resolve();\n delete this.resolvingFunctions;\n }\n }\n async preservingPermanentElements(callback) {\n await Bardo.preservingPermanentElements(this, this.permanentElementMap, callback);\n }\n focusFirstAutofocusableElement() {\n const element = this.connectedSnapshot.firstAutofocusableElement;\n if (elementIsFocusable(element)) {\n element.focus();\n }\n }\n enteringBardo(currentPermanentElement) {\n if (this.activeElement)\n return;\n if (currentPermanentElement.contains(this.currentSnapshot.activeElement)) {\n this.activeElement = this.currentSnapshot.activeElement;\n }\n }\n leavingBardo(currentPermanentElement) {\n if (currentPermanentElement.contains(this.activeElement) && this.activeElement instanceof HTMLElement) {\n this.activeElement.focus();\n this.activeElement = null;\n }\n }\n get connectedSnapshot() {\n return this.newSnapshot.isConnected ? this.newSnapshot : this.currentSnapshot;\n }\n get currentElement() {\n return this.currentSnapshot.element;\n }\n get newElement() {\n return this.newSnapshot.element;\n }\n get permanentElementMap() {\n return this.currentSnapshot.getPermanentElementMapForSnapshot(this.newSnapshot);\n }\n}\nfunction elementIsFocusable(element) {\n return element && typeof element.focus == \"function\";\n}\n\nclass FrameRenderer extends Renderer {\n static renderElement(currentElement, newElement) {\n var _a;\n const destinationRange = document.createRange();\n destinationRange.selectNodeContents(currentElement);\n destinationRange.deleteContents();\n const frameElement = newElement;\n const sourceRange = (_a = frameElement.ownerDocument) === null || _a === void 0 ? void 0 : _a.createRange();\n if (sourceRange) {\n sourceRange.selectNodeContents(frameElement);\n currentElement.appendChild(sourceRange.extractContents());\n }\n }\n constructor(delegate, currentSnapshot, newSnapshot, renderElement, isPreview, willRender = true) {\n super(currentSnapshot, newSnapshot, renderElement, isPreview, willRender);\n this.delegate = delegate;\n }\n get shouldRender() {\n return true;\n }\n async render() {\n await nextAnimationFrame();\n this.preservingPermanentElements(() => {\n this.loadFrameElement();\n });\n this.scrollFrameIntoView();\n await nextAnimationFrame();\n this.focusFirstAutofocusableElement();\n await nextAnimationFrame();\n this.activateScriptElements();\n }\n loadFrameElement() {\n this.delegate.willRenderFrame(this.currentElement, this.newElement);\n this.renderElement(this.currentElement, this.newElement);\n }\n scrollFrameIntoView() {\n if (this.currentElement.autoscroll || this.newElement.autoscroll) {\n const element = this.currentElement.firstElementChild;\n const block = readScrollLogicalPosition(this.currentElement.getAttribute(\"data-autoscroll-block\"), \"end\");\n const behavior = readScrollBehavior(this.currentElement.getAttribute(\"data-autoscroll-behavior\"), \"auto\");\n if (element) {\n element.scrollIntoView({ block, behavior });\n return true;\n }\n }\n return false;\n }\n activateScriptElements() {\n for (const inertScriptElement of this.newScriptElements) {\n const activatedScriptElement = activateScriptElement(inertScriptElement);\n inertScriptElement.replaceWith(activatedScriptElement);\n }\n }\n get newScriptElements() {\n return this.currentElement.querySelectorAll(\"script\");\n }\n}\nfunction readScrollLogicalPosition(value, defaultValue) {\n if (value == \"end\" || value == \"start\" || value == \"center\" || value == \"nearest\") {\n return value;\n }\n else {\n return defaultValue;\n }\n}\nfunction readScrollBehavior(value, defaultValue) {\n if (value == \"auto\" || value == \"smooth\") {\n return value;\n }\n else {\n return defaultValue;\n }\n}\n\nclass ProgressBar {\n static get defaultCSS() {\n return unindent `\n .turbo-progress-bar {\n position: fixed;\n display: block;\n top: 0;\n left: 0;\n height: 3px;\n background: #0076ff;\n z-index: 2147483647;\n transition:\n width ${ProgressBar.animationDuration}ms ease-out,\n opacity ${ProgressBar.animationDuration / 2}ms ${ProgressBar.animationDuration / 2}ms ease-in;\n transform: translate3d(0, 0, 0);\n }\n `;\n }\n constructor() {\n this.hiding = false;\n this.value = 0;\n this.visible = false;\n this.trickle = () => {\n this.setValue(this.value + Math.random() / 100);\n };\n this.stylesheetElement = this.createStylesheetElement();\n this.progressElement = this.createProgressElement();\n this.installStylesheetElement();\n this.setValue(0);\n }\n show() {\n if (!this.visible) {\n this.visible = true;\n this.installProgressElement();\n this.startTrickling();\n }\n }\n hide() {\n if (this.visible && !this.hiding) {\n this.hiding = true;\n this.fadeProgressElement(() => {\n this.uninstallProgressElement();\n this.stopTrickling();\n this.visible = false;\n this.hiding = false;\n });\n }\n }\n setValue(value) {\n this.value = value;\n this.refresh();\n }\n installStylesheetElement() {\n document.head.insertBefore(this.stylesheetElement, document.head.firstChild);\n }\n installProgressElement() {\n this.progressElement.style.width = \"0\";\n this.progressElement.style.opacity = \"1\";\n document.documentElement.insertBefore(this.progressElement, document.body);\n this.refresh();\n }\n fadeProgressElement(callback) {\n this.progressElement.style.opacity = \"0\";\n setTimeout(callback, ProgressBar.animationDuration * 1.5);\n }\n uninstallProgressElement() {\n if (this.progressElement.parentNode) {\n document.documentElement.removeChild(this.progressElement);\n }\n }\n startTrickling() {\n if (!this.trickleInterval) {\n this.trickleInterval = window.setInterval(this.trickle, ProgressBar.animationDuration);\n }\n }\n stopTrickling() {\n window.clearInterval(this.trickleInterval);\n delete this.trickleInterval;\n }\n refresh() {\n requestAnimationFrame(() => {\n this.progressElement.style.width = `${10 + this.value * 90}%`;\n });\n }\n createStylesheetElement() {\n const element = document.createElement(\"style\");\n element.type = \"text/css\";\n element.textContent = ProgressBar.defaultCSS;\n if (this.cspNonce) {\n element.nonce = this.cspNonce;\n }\n return element;\n }\n createProgressElement() {\n const element = document.createElement(\"div\");\n element.className = \"turbo-progress-bar\";\n return element;\n }\n get cspNonce() {\n return getMetaContent(\"csp-nonce\");\n }\n}\nProgressBar.animationDuration = 300;\n\nclass HeadSnapshot extends Snapshot {\n constructor() {\n super(...arguments);\n this.detailsByOuterHTML = this.children\n .filter((element) => !elementIsNoscript(element))\n .map((element) => elementWithoutNonce(element))\n .reduce((result, element) => {\n const { outerHTML } = element;\n const details = outerHTML in result\n ? result[outerHTML]\n : {\n type: elementType(element),\n tracked: elementIsTracked(element),\n elements: [],\n };\n return Object.assign(Object.assign({}, result), { [outerHTML]: Object.assign(Object.assign({}, details), { elements: [...details.elements, element] }) });\n }, {});\n }\n get trackedElementSignature() {\n return Object.keys(this.detailsByOuterHTML)\n .filter((outerHTML) => this.detailsByOuterHTML[outerHTML].tracked)\n .join(\"\");\n }\n getScriptElementsNotInSnapshot(snapshot) {\n return this.getElementsMatchingTypeNotInSnapshot(\"script\", snapshot);\n }\n getStylesheetElementsNotInSnapshot(snapshot) {\n return this.getElementsMatchingTypeNotInSnapshot(\"stylesheet\", snapshot);\n }\n getElementsMatchingTypeNotInSnapshot(matchedType, snapshot) {\n return Object.keys(this.detailsByOuterHTML)\n .filter((outerHTML) => !(outerHTML in snapshot.detailsByOuterHTML))\n .map((outerHTML) => this.detailsByOuterHTML[outerHTML])\n .filter(({ type }) => type == matchedType)\n .map(({ elements: [element] }) => element);\n }\n get provisionalElements() {\n return Object.keys(this.detailsByOuterHTML).reduce((result, outerHTML) => {\n const { type, tracked, elements } = this.detailsByOuterHTML[outerHTML];\n if (type == null && !tracked) {\n return [...result, ...elements];\n }\n else if (elements.length > 1) {\n return [...result, ...elements.slice(1)];\n }\n else {\n return result;\n }\n }, []);\n }\n getMetaValue(name) {\n const element = this.findMetaElementByName(name);\n return element ? element.getAttribute(\"content\") : null;\n }\n findMetaElementByName(name) {\n return Object.keys(this.detailsByOuterHTML).reduce((result, outerHTML) => {\n const { elements: [element], } = this.detailsByOuterHTML[outerHTML];\n return elementIsMetaElementWithName(element, name) ? element : result;\n }, undefined);\n }\n}\nfunction elementType(element) {\n if (elementIsScript(element)) {\n return \"script\";\n }\n else if (elementIsStylesheet(element)) {\n return \"stylesheet\";\n }\n}\nfunction elementIsTracked(element) {\n return element.getAttribute(\"data-turbo-track\") == \"reload\";\n}\nfunction elementIsScript(element) {\n const tagName = element.localName;\n return tagName == \"script\";\n}\nfunction elementIsNoscript(element) {\n const tagName = element.localName;\n return tagName == \"noscript\";\n}\nfunction elementIsStylesheet(element) {\n const tagName = element.localName;\n return tagName == \"style\" || (tagName == \"link\" && element.getAttribute(\"rel\") == \"stylesheet\");\n}\nfunction elementIsMetaElementWithName(element, name) {\n const tagName = element.localName;\n return tagName == \"meta\" && element.getAttribute(\"name\") == name;\n}\nfunction elementWithoutNonce(element) {\n if (element.hasAttribute(\"nonce\")) {\n element.setAttribute(\"nonce\", \"\");\n }\n return element;\n}\n\nclass PageSnapshot extends Snapshot {\n static fromHTMLString(html = \"\") {\n return this.fromDocument(parseHTMLDocument(html));\n }\n static fromElement(element) {\n return this.fromDocument(element.ownerDocument);\n }\n static fromDocument({ head, body }) {\n return new this(body, new HeadSnapshot(head));\n }\n constructor(element, headSnapshot) {\n super(element);\n this.headSnapshot = headSnapshot;\n }\n clone() {\n const clonedElement = this.element.cloneNode(true);\n const selectElements = this.element.querySelectorAll(\"select\");\n const clonedSelectElements = clonedElement.querySelectorAll(\"select\");\n for (const [index, source] of selectElements.entries()) {\n const clone = clonedSelectElements[index];\n for (const option of clone.selectedOptions)\n option.selected = false;\n for (const option of source.selectedOptions)\n clone.options[option.index].selected = true;\n }\n for (const clonedPasswordInput of clonedElement.querySelectorAll('input[type=\"password\"]')) {\n clonedPasswordInput.value = \"\";\n }\n return new PageSnapshot(clonedElement, this.headSnapshot);\n }\n get headElement() {\n return this.headSnapshot.element;\n }\n get rootLocation() {\n var _a;\n const root = (_a = this.getSetting(\"root\")) !== null && _a !== void 0 ? _a : \"/\";\n return expandURL(root);\n }\n get cacheControlValue() {\n return this.getSetting(\"cache-control\");\n }\n get isPreviewable() {\n return this.cacheControlValue != \"no-preview\";\n }\n get isCacheable() {\n return this.cacheControlValue != \"no-cache\";\n }\n get isVisitable() {\n return this.getSetting(\"visit-control\") != \"reload\";\n }\n getSetting(name) {\n return this.headSnapshot.getMetaValue(`turbo-${name}`);\n }\n}\n\nvar TimingMetric;\n(function (TimingMetric) {\n TimingMetric[\"visitStart\"] = \"visitStart\";\n TimingMetric[\"requestStart\"] = \"requestStart\";\n TimingMetric[\"requestEnd\"] = \"requestEnd\";\n TimingMetric[\"visitEnd\"] = \"visitEnd\";\n})(TimingMetric || (TimingMetric = {}));\nvar VisitState;\n(function (VisitState) {\n VisitState[\"initialized\"] = \"initialized\";\n VisitState[\"started\"] = \"started\";\n VisitState[\"canceled\"] = \"canceled\";\n VisitState[\"failed\"] = \"failed\";\n VisitState[\"completed\"] = \"completed\";\n})(VisitState || (VisitState = {}));\nconst defaultOptions = {\n action: \"advance\",\n historyChanged: false,\n visitCachedSnapshot: () => { },\n willRender: true,\n updateHistory: true,\n shouldCacheSnapshot: true,\n acceptsStreamResponse: false,\n};\nvar SystemStatusCode;\n(function (SystemStatusCode) {\n SystemStatusCode[SystemStatusCode[\"networkFailure\"] = 0] = \"networkFailure\";\n SystemStatusCode[SystemStatusCode[\"timeoutFailure\"] = -1] = \"timeoutFailure\";\n SystemStatusCode[SystemStatusCode[\"contentTypeMismatch\"] = -2] = \"contentTypeMismatch\";\n})(SystemStatusCode || (SystemStatusCode = {}));\nclass Visit {\n constructor(delegate, location, restorationIdentifier, options = {}) {\n this.identifier = uuid();\n this.timingMetrics = {};\n this.followedRedirect = false;\n this.historyChanged = false;\n this.scrolled = false;\n this.shouldCacheSnapshot = true;\n this.acceptsStreamResponse = false;\n this.snapshotCached = false;\n this.state = VisitState.initialized;\n this.delegate = delegate;\n this.location = location;\n this.restorationIdentifier = restorationIdentifier || uuid();\n const { action, historyChanged, referrer, snapshot, snapshotHTML, response, visitCachedSnapshot, willRender, updateHistory, shouldCacheSnapshot, acceptsStreamResponse, } = Object.assign(Object.assign({}, defaultOptions), options);\n this.action = action;\n this.historyChanged = historyChanged;\n this.referrer = referrer;\n this.snapshot = snapshot;\n this.snapshotHTML = snapshotHTML;\n this.response = response;\n this.isSamePage = this.delegate.locationWithActionIsSamePage(this.location, this.action);\n this.visitCachedSnapshot = visitCachedSnapshot;\n this.willRender = willRender;\n this.updateHistory = updateHistory;\n this.scrolled = !willRender;\n this.shouldCacheSnapshot = shouldCacheSnapshot;\n this.acceptsStreamResponse = acceptsStreamResponse;\n }\n get adapter() {\n return this.delegate.adapter;\n }\n get view() {\n return this.delegate.view;\n }\n get history() {\n return this.delegate.history;\n }\n get restorationData() {\n return this.history.getRestorationDataForIdentifier(this.restorationIdentifier);\n }\n get silent() {\n return this.isSamePage;\n }\n start() {\n if (this.state == VisitState.initialized) {\n this.recordTimingMetric(TimingMetric.visitStart);\n this.state = VisitState.started;\n this.adapter.visitStarted(this);\n this.delegate.visitStarted(this);\n }\n }\n cancel() {\n if (this.state == VisitState.started) {\n if (this.request) {\n this.request.cancel();\n }\n this.cancelRender();\n this.state = VisitState.canceled;\n }\n }\n complete() {\n if (this.state == VisitState.started) {\n this.recordTimingMetric(TimingMetric.visitEnd);\n this.state = VisitState.completed;\n this.followRedirect();\n if (!this.followedRedirect) {\n this.adapter.visitCompleted(this);\n this.delegate.visitCompleted(this);\n }\n }\n }\n fail() {\n if (this.state == VisitState.started) {\n this.state = VisitState.failed;\n this.adapter.visitFailed(this);\n }\n }\n changeHistory() {\n var _a;\n if (!this.historyChanged && this.updateHistory) {\n const actionForHistory = this.location.href === ((_a = this.referrer) === null || _a === void 0 ? void 0 : _a.href) ? \"replace\" : this.action;\n const method = getHistoryMethodForAction(actionForHistory);\n this.history.update(method, this.location, this.restorationIdentifier);\n this.historyChanged = true;\n }\n }\n issueRequest() {\n if (this.hasPreloadedResponse()) {\n this.simulateRequest();\n }\n else if (this.shouldIssueRequest() && !this.request) {\n this.request = new FetchRequest(this, FetchMethod.get, this.location);\n this.request.perform();\n }\n }\n simulateRequest() {\n if (this.response) {\n this.startRequest();\n this.recordResponse();\n this.finishRequest();\n }\n }\n startRequest() {\n this.recordTimingMetric(TimingMetric.requestStart);\n this.adapter.visitRequestStarted(this);\n }\n recordResponse(response = this.response) {\n this.response = response;\n if (response) {\n const { statusCode } = response;\n if (isSuccessful(statusCode)) {\n this.adapter.visitRequestCompleted(this);\n }\n else {\n this.adapter.visitRequestFailedWithStatusCode(this, statusCode);\n }\n }\n }\n finishRequest() {\n this.recordTimingMetric(TimingMetric.requestEnd);\n this.adapter.visitRequestFinished(this);\n }\n loadResponse() {\n if (this.response) {\n const { statusCode, responseHTML } = this.response;\n this.render(async () => {\n if (this.shouldCacheSnapshot)\n this.cacheSnapshot();\n if (this.view.renderPromise)\n await this.view.renderPromise;\n if (isSuccessful(statusCode) && responseHTML != null) {\n await this.view.renderPage(PageSnapshot.fromHTMLString(responseHTML), false, this.willRender, this);\n this.performScroll();\n this.adapter.visitRendered(this);\n this.complete();\n }\n else {\n await this.view.renderError(PageSnapshot.fromHTMLString(responseHTML), this);\n this.adapter.visitRendered(this);\n this.fail();\n }\n });\n }\n }\n getCachedSnapshot() {\n const snapshot = this.view.getCachedSnapshotForLocation(this.location) || this.getPreloadedSnapshot();\n if (snapshot && (!getAnchor(this.location) || snapshot.hasAnchor(getAnchor(this.location)))) {\n if (this.action == \"restore\" || snapshot.isPreviewable) {\n return snapshot;\n }\n }\n }\n getPreloadedSnapshot() {\n if (this.snapshotHTML) {\n return PageSnapshot.fromHTMLString(this.snapshotHTML);\n }\n }\n hasCachedSnapshot() {\n return this.getCachedSnapshot() != null;\n }\n loadCachedSnapshot() {\n const snapshot = this.getCachedSnapshot();\n if (snapshot) {\n const isPreview = this.shouldIssueRequest();\n this.render(async () => {\n this.cacheSnapshot();\n if (this.isSamePage) {\n this.adapter.visitRendered(this);\n }\n else {\n if (this.view.renderPromise)\n await this.view.renderPromise;\n await this.view.renderPage(snapshot, isPreview, this.willRender, this);\n this.performScroll();\n this.adapter.visitRendered(this);\n if (!isPreview) {\n this.complete();\n }\n }\n });\n }\n }\n followRedirect() {\n var _a;\n if (this.redirectedToLocation && !this.followedRedirect && ((_a = this.response) === null || _a === void 0 ? void 0 : _a.redirected)) {\n this.adapter.visitProposedToLocation(this.redirectedToLocation, {\n action: \"replace\",\n response: this.response,\n shouldCacheSnapshot: false,\n willRender: false,\n });\n this.followedRedirect = true;\n }\n }\n goToSamePageAnchor() {\n if (this.isSamePage) {\n this.render(async () => {\n this.cacheSnapshot();\n this.performScroll();\n this.changeHistory();\n this.adapter.visitRendered(this);\n });\n }\n }\n prepareRequest(request) {\n if (this.acceptsStreamResponse) {\n request.acceptResponseType(StreamMessage.contentType);\n }\n }\n requestStarted() {\n this.startRequest();\n }\n requestPreventedHandlingResponse(_request, _response) { }\n async requestSucceededWithResponse(request, response) {\n const responseHTML = await response.responseHTML;\n const { redirected, statusCode } = response;\n if (responseHTML == undefined) {\n this.recordResponse({\n statusCode: SystemStatusCode.contentTypeMismatch,\n redirected,\n });\n }\n else {\n this.redirectedToLocation = response.redirected ? response.location : undefined;\n this.recordResponse({ statusCode: statusCode, responseHTML, redirected });\n }\n }\n async requestFailedWithResponse(request, response) {\n const responseHTML = await response.responseHTML;\n const { redirected, statusCode } = response;\n if (responseHTML == undefined) {\n this.recordResponse({\n statusCode: SystemStatusCode.contentTypeMismatch,\n redirected,\n });\n }\n else {\n this.recordResponse({ statusCode: statusCode, responseHTML, redirected });\n }\n }\n requestErrored(_request, _error) {\n this.recordResponse({\n statusCode: SystemStatusCode.networkFailure,\n redirected: false,\n });\n }\n requestFinished() {\n this.finishRequest();\n }\n performScroll() {\n if (!this.scrolled && !this.view.forceReloaded) {\n if (this.action == \"restore\") {\n this.scrollToRestoredPosition() || this.scrollToAnchor() || this.view.scrollToTop();\n }\n else {\n this.scrollToAnchor() || this.view.scrollToTop();\n }\n if (this.isSamePage) {\n this.delegate.visitScrolledToSamePageLocation(this.view.lastRenderedLocation, this.location);\n }\n this.scrolled = true;\n }\n }\n scrollToRestoredPosition() {\n const { scrollPosition } = this.restorationData;\n if (scrollPosition) {\n this.view.scrollToPosition(scrollPosition);\n return true;\n }\n }\n scrollToAnchor() {\n const anchor = getAnchor(this.location);\n if (anchor != null) {\n this.view.scrollToAnchor(anchor);\n return true;\n }\n }\n recordTimingMetric(metric) {\n this.timingMetrics[metric] = new Date().getTime();\n }\n getTimingMetrics() {\n return Object.assign({}, this.timingMetrics);\n }\n getHistoryMethodForAction(action) {\n switch (action) {\n case \"replace\":\n return history.replaceState;\n case \"advance\":\n case \"restore\":\n return history.pushState;\n }\n }\n hasPreloadedResponse() {\n return typeof this.response == \"object\";\n }\n shouldIssueRequest() {\n if (this.isSamePage) {\n return false;\n }\n else if (this.action == \"restore\") {\n return !this.hasCachedSnapshot();\n }\n else {\n return this.willRender;\n }\n }\n cacheSnapshot() {\n if (!this.snapshotCached) {\n this.view.cacheSnapshot(this.snapshot).then((snapshot) => snapshot && this.visitCachedSnapshot(snapshot));\n this.snapshotCached = true;\n }\n }\n async render(callback) {\n this.cancelRender();\n await new Promise((resolve) => {\n this.frame = requestAnimationFrame(() => resolve());\n });\n await callback();\n delete this.frame;\n }\n cancelRender() {\n if (this.frame) {\n cancelAnimationFrame(this.frame);\n delete this.frame;\n }\n }\n}\nfunction isSuccessful(statusCode) {\n return statusCode >= 200 && statusCode < 300;\n}\n\nclass BrowserAdapter {\n constructor(session) {\n this.progressBar = new ProgressBar();\n this.showProgressBar = () => {\n this.progressBar.show();\n };\n this.session = session;\n }\n visitProposedToLocation(location, options) {\n this.navigator.startVisit(location, (options === null || options === void 0 ? void 0 : options.restorationIdentifier) || uuid(), options);\n }\n visitStarted(visit) {\n this.location = visit.location;\n visit.loadCachedSnapshot();\n visit.issueRequest();\n visit.goToSamePageAnchor();\n }\n visitRequestStarted(visit) {\n this.progressBar.setValue(0);\n if (visit.hasCachedSnapshot() || visit.action != \"restore\") {\n this.showVisitProgressBarAfterDelay();\n }\n else {\n this.showProgressBar();\n }\n }\n visitRequestCompleted(visit) {\n visit.loadResponse();\n }\n visitRequestFailedWithStatusCode(visit, statusCode) {\n switch (statusCode) {\n case SystemStatusCode.networkFailure:\n case SystemStatusCode.timeoutFailure:\n case SystemStatusCode.contentTypeMismatch:\n return this.reload({\n reason: \"request_failed\",\n context: {\n statusCode,\n },\n });\n default:\n return visit.loadResponse();\n }\n }\n visitRequestFinished(_visit) {\n this.progressBar.setValue(1);\n this.hideVisitProgressBar();\n }\n visitCompleted(_visit) { }\n pageInvalidated(reason) {\n this.reload(reason);\n }\n visitFailed(_visit) { }\n visitRendered(_visit) { }\n formSubmissionStarted(_formSubmission) {\n this.progressBar.setValue(0);\n this.showFormProgressBarAfterDelay();\n }\n formSubmissionFinished(_formSubmission) {\n this.progressBar.setValue(1);\n this.hideFormProgressBar();\n }\n showVisitProgressBarAfterDelay() {\n this.visitProgressBarTimeout = window.setTimeout(this.showProgressBar, this.session.progressBarDelay);\n }\n hideVisitProgressBar() {\n this.progressBar.hide();\n if (this.visitProgressBarTimeout != null) {\n window.clearTimeout(this.visitProgressBarTimeout);\n delete this.visitProgressBarTimeout;\n }\n }\n showFormProgressBarAfterDelay() {\n if (this.formProgressBarTimeout == null) {\n this.formProgressBarTimeout = window.setTimeout(this.showProgressBar, this.session.progressBarDelay);\n }\n }\n hideFormProgressBar() {\n this.progressBar.hide();\n if (this.formProgressBarTimeout != null) {\n window.clearTimeout(this.formProgressBarTimeout);\n delete this.formProgressBarTimeout;\n }\n }\n reload(reason) {\n var _a;\n dispatch(\"turbo:reload\", { detail: reason });\n window.location.href = ((_a = this.location) === null || _a === void 0 ? void 0 : _a.toString()) || window.location.href;\n }\n get navigator() {\n return this.session.navigator;\n }\n}\n\nclass CacheObserver {\n constructor() {\n this.selector = \"[data-turbo-temporary]\";\n this.deprecatedSelector = \"[data-turbo-cache=false]\";\n this.started = false;\n this.removeTemporaryElements = ((_event) => {\n for (const element of this.temporaryElements) {\n element.remove();\n }\n });\n }\n start() {\n if (!this.started) {\n this.started = true;\n addEventListener(\"turbo:before-cache\", this.removeTemporaryElements, false);\n }\n }\n stop() {\n if (this.started) {\n this.started = false;\n removeEventListener(\"turbo:before-cache\", this.removeTemporaryElements, false);\n }\n }\n get temporaryElements() {\n return [...document.querySelectorAll(this.selector), ...this.temporaryElementsWithDeprecation];\n }\n get temporaryElementsWithDeprecation() {\n const elements = document.querySelectorAll(this.deprecatedSelector);\n if (elements.length) {\n console.warn(`The ${this.deprecatedSelector} selector is deprecated and will be removed in a future version. Use ${this.selector} instead.`);\n }\n return [...elements];\n }\n}\n\nclass FrameRedirector {\n constructor(session, element) {\n this.session = session;\n this.element = element;\n this.linkInterceptor = new LinkInterceptor(this, element);\n this.formSubmitObserver = new FormSubmitObserver(this, element);\n }\n start() {\n this.linkInterceptor.start();\n this.formSubmitObserver.start();\n }\n stop() {\n this.linkInterceptor.stop();\n this.formSubmitObserver.stop();\n }\n shouldInterceptLinkClick(element, _location, _event) {\n return this.shouldRedirect(element);\n }\n linkClickIntercepted(element, url, event) {\n const frame = this.findFrameElement(element);\n if (frame) {\n frame.delegate.linkClickIntercepted(element, url, event);\n }\n }\n willSubmitForm(element, submitter) {\n return (element.closest(\"turbo-frame\") == null &&\n this.shouldSubmit(element, submitter) &&\n this.shouldRedirect(element, submitter));\n }\n formSubmitted(element, submitter) {\n const frame = this.findFrameElement(element, submitter);\n if (frame) {\n frame.delegate.formSubmitted(element, submitter);\n }\n }\n shouldSubmit(form, submitter) {\n var _a;\n const action = getAction(form, submitter);\n const meta = this.element.ownerDocument.querySelector(`meta[name=\"turbo-root\"]`);\n const rootLocation = expandURL((_a = meta === null || meta === void 0 ? void 0 : meta.content) !== null && _a !== void 0 ? _a : \"/\");\n return this.shouldRedirect(form, submitter) && locationIsVisitable(action, rootLocation);\n }\n shouldRedirect(element, submitter) {\n const isNavigatable = element instanceof HTMLFormElement\n ? this.session.submissionIsNavigatable(element, submitter)\n : this.session.elementIsNavigatable(element);\n if (isNavigatable) {\n const frame = this.findFrameElement(element, submitter);\n return frame ? frame != element.closest(\"turbo-frame\") : false;\n }\n else {\n return false;\n }\n }\n findFrameElement(element, submitter) {\n const id = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute(\"data-turbo-frame\")) || element.getAttribute(\"data-turbo-frame\");\n if (id && id != \"_top\") {\n const frame = this.element.querySelector(`#${id}:not([disabled])`);\n if (frame instanceof FrameElement) {\n return frame;\n }\n }\n }\n}\n\nclass History {\n constructor(delegate) {\n this.restorationIdentifier = uuid();\n this.restorationData = {};\n this.started = false;\n this.pageLoaded = false;\n this.onPopState = (event) => {\n if (this.shouldHandlePopState()) {\n const { turbo } = event.state || {};\n if (turbo) {\n this.location = new URL(window.location.href);\n const { restorationIdentifier } = turbo;\n this.restorationIdentifier = restorationIdentifier;\n this.delegate.historyPoppedToLocationWithRestorationIdentifier(this.location, restorationIdentifier);\n }\n }\n };\n this.onPageLoad = async (_event) => {\n await nextMicrotask();\n this.pageLoaded = true;\n };\n this.delegate = delegate;\n }\n start() {\n if (!this.started) {\n addEventListener(\"popstate\", this.onPopState, false);\n addEventListener(\"load\", this.onPageLoad, false);\n this.started = true;\n this.replace(new URL(window.location.href));\n }\n }\n stop() {\n if (this.started) {\n removeEventListener(\"popstate\", this.onPopState, false);\n removeEventListener(\"load\", this.onPageLoad, false);\n this.started = false;\n }\n }\n push(location, restorationIdentifier) {\n this.update(history.pushState, location, restorationIdentifier);\n }\n replace(location, restorationIdentifier) {\n this.update(history.replaceState, location, restorationIdentifier);\n }\n update(method, location, restorationIdentifier = uuid()) {\n const state = { turbo: { restorationIdentifier } };\n method.call(history, state, \"\", location.href);\n this.location = location;\n this.restorationIdentifier = restorationIdentifier;\n }\n getRestorationDataForIdentifier(restorationIdentifier) {\n return this.restorationData[restorationIdentifier] || {};\n }\n updateRestorationData(additionalData) {\n const { restorationIdentifier } = this;\n const restorationData = this.restorationData[restorationIdentifier];\n this.restorationData[restorationIdentifier] = Object.assign(Object.assign({}, restorationData), additionalData);\n }\n assumeControlOfScrollRestoration() {\n var _a;\n if (!this.previousScrollRestoration) {\n this.previousScrollRestoration = (_a = history.scrollRestoration) !== null && _a !== void 0 ? _a : \"auto\";\n history.scrollRestoration = \"manual\";\n }\n }\n relinquishControlOfScrollRestoration() {\n if (this.previousScrollRestoration) {\n history.scrollRestoration = this.previousScrollRestoration;\n delete this.previousScrollRestoration;\n }\n }\n shouldHandlePopState() {\n return this.pageIsLoaded();\n }\n pageIsLoaded() {\n return this.pageLoaded || document.readyState == \"complete\";\n }\n}\n\nclass Navigator {\n constructor(delegate) {\n this.delegate = delegate;\n }\n proposeVisit(location, options = {}) {\n if (this.delegate.allowsVisitingLocationWithAction(location, options.action)) {\n if (locationIsVisitable(location, this.view.snapshot.rootLocation)) {\n this.delegate.visitProposedToLocation(location, options);\n }\n else {\n window.location.href = location.toString();\n }\n }\n }\n startVisit(locatable, restorationIdentifier, options = {}) {\n this.stop();\n this.currentVisit = new Visit(this, expandURL(locatable), restorationIdentifier, Object.assign({ referrer: this.location }, options));\n this.currentVisit.start();\n }\n submitForm(form, submitter) {\n this.stop();\n this.formSubmission = new FormSubmission(this, form, submitter, true);\n this.formSubmission.start();\n }\n stop() {\n if (this.formSubmission) {\n this.formSubmission.stop();\n delete this.formSubmission;\n }\n if (this.currentVisit) {\n this.currentVisit.cancel();\n delete this.currentVisit;\n }\n }\n get adapter() {\n return this.delegate.adapter;\n }\n get view() {\n return this.delegate.view;\n }\n get history() {\n return this.delegate.history;\n }\n formSubmissionStarted(formSubmission) {\n if (typeof this.adapter.formSubmissionStarted === \"function\") {\n this.adapter.formSubmissionStarted(formSubmission);\n }\n }\n async formSubmissionSucceededWithResponse(formSubmission, fetchResponse) {\n if (formSubmission == this.formSubmission) {\n const responseHTML = await fetchResponse.responseHTML;\n if (responseHTML) {\n const shouldCacheSnapshot = formSubmission.isSafe;\n if (!shouldCacheSnapshot) {\n this.view.clearSnapshotCache();\n }\n const { statusCode, redirected } = fetchResponse;\n const action = this.getActionForFormSubmission(formSubmission);\n const visitOptions = {\n action,\n shouldCacheSnapshot,\n response: { statusCode, responseHTML, redirected },\n };\n this.proposeVisit(fetchResponse.location, visitOptions);\n }\n }\n }\n async formSubmissionFailedWithResponse(formSubmission, fetchResponse) {\n const responseHTML = await fetchResponse.responseHTML;\n if (responseHTML) {\n const snapshot = PageSnapshot.fromHTMLString(responseHTML);\n if (fetchResponse.serverError) {\n await this.view.renderError(snapshot, this.currentVisit);\n }\n else {\n await this.view.renderPage(snapshot, false, true, this.currentVisit);\n }\n this.view.scrollToTop();\n this.view.clearSnapshotCache();\n }\n }\n formSubmissionErrored(formSubmission, error) {\n console.error(error);\n }\n formSubmissionFinished(formSubmission) {\n if (typeof this.adapter.formSubmissionFinished === \"function\") {\n this.adapter.formSubmissionFinished(formSubmission);\n }\n }\n visitStarted(visit) {\n this.delegate.visitStarted(visit);\n }\n visitCompleted(visit) {\n this.delegate.visitCompleted(visit);\n }\n locationWithActionIsSamePage(location, action) {\n const anchor = getAnchor(location);\n const currentAnchor = getAnchor(this.view.lastRenderedLocation);\n const isRestorationToTop = action === \"restore\" && typeof anchor === \"undefined\";\n return (action !== \"replace\" &&\n getRequestURL(location) === getRequestURL(this.view.lastRenderedLocation) &&\n (isRestorationToTop || (anchor != null && anchor !== currentAnchor)));\n }\n visitScrolledToSamePageLocation(oldURL, newURL) {\n this.delegate.visitScrolledToSamePageLocation(oldURL, newURL);\n }\n get location() {\n return this.history.location;\n }\n get restorationIdentifier() {\n return this.history.restorationIdentifier;\n }\n getActionForFormSubmission({ submitter, formElement }) {\n return getVisitAction(submitter, formElement) || \"advance\";\n }\n}\n\nvar PageStage;\n(function (PageStage) {\n PageStage[PageStage[\"initial\"] = 0] = \"initial\";\n PageStage[PageStage[\"loading\"] = 1] = \"loading\";\n PageStage[PageStage[\"interactive\"] = 2] = \"interactive\";\n PageStage[PageStage[\"complete\"] = 3] = \"complete\";\n})(PageStage || (PageStage = {}));\nclass PageObserver {\n constructor(delegate) {\n this.stage = PageStage.initial;\n this.started = false;\n this.interpretReadyState = () => {\n const { readyState } = this;\n if (readyState == \"interactive\") {\n this.pageIsInteractive();\n }\n else if (readyState == \"complete\") {\n this.pageIsComplete();\n }\n };\n this.pageWillUnload = () => {\n this.delegate.pageWillUnload();\n };\n this.delegate = delegate;\n }\n start() {\n if (!this.started) {\n if (this.stage == PageStage.initial) {\n this.stage = PageStage.loading;\n }\n document.addEventListener(\"readystatechange\", this.interpretReadyState, false);\n addEventListener(\"pagehide\", this.pageWillUnload, false);\n this.started = true;\n }\n }\n stop() {\n if (this.started) {\n document.removeEventListener(\"readystatechange\", this.interpretReadyState, false);\n removeEventListener(\"pagehide\", this.pageWillUnload, false);\n this.started = false;\n }\n }\n pageIsInteractive() {\n if (this.stage == PageStage.loading) {\n this.stage = PageStage.interactive;\n this.delegate.pageBecameInteractive();\n }\n }\n pageIsComplete() {\n this.pageIsInteractive();\n if (this.stage == PageStage.interactive) {\n this.stage = PageStage.complete;\n this.delegate.pageLoaded();\n }\n }\n get readyState() {\n return document.readyState;\n }\n}\n\nclass ScrollObserver {\n constructor(delegate) {\n this.started = false;\n this.onScroll = () => {\n this.updatePosition({ x: window.pageXOffset, y: window.pageYOffset });\n };\n this.delegate = delegate;\n }\n start() {\n if (!this.started) {\n addEventListener(\"scroll\", this.onScroll, false);\n this.onScroll();\n this.started = true;\n }\n }\n stop() {\n if (this.started) {\n removeEventListener(\"scroll\", this.onScroll, false);\n this.started = false;\n }\n }\n updatePosition(position) {\n this.delegate.scrollPositionChanged(position);\n }\n}\n\nclass StreamMessageRenderer {\n render({ fragment }) {\n Bardo.preservingPermanentElements(this, getPermanentElementMapForFragment(fragment), () => document.documentElement.appendChild(fragment));\n }\n enteringBardo(currentPermanentElement, newPermanentElement) {\n newPermanentElement.replaceWith(currentPermanentElement.cloneNode(true));\n }\n leavingBardo() { }\n}\nfunction getPermanentElementMapForFragment(fragment) {\n const permanentElementsInDocument = queryPermanentElementsAll(document.documentElement);\n const permanentElementMap = {};\n for (const permanentElementInDocument of permanentElementsInDocument) {\n const { id } = permanentElementInDocument;\n for (const streamElement of fragment.querySelectorAll(\"turbo-stream\")) {\n const elementInStream = getPermanentElementById(streamElement.templateElement.content, id);\n if (elementInStream) {\n permanentElementMap[id] = [permanentElementInDocument, elementInStream];\n }\n }\n }\n return permanentElementMap;\n}\n\nclass StreamObserver {\n constructor(delegate) {\n this.sources = new Set();\n this.started = false;\n this.inspectFetchResponse = ((event) => {\n const response = fetchResponseFromEvent(event);\n if (response && fetchResponseIsStream(response)) {\n event.preventDefault();\n this.receiveMessageResponse(response);\n }\n });\n this.receiveMessageEvent = (event) => {\n if (this.started && typeof event.data == \"string\") {\n this.receiveMessageHTML(event.data);\n }\n };\n this.delegate = delegate;\n }\n start() {\n if (!this.started) {\n this.started = true;\n addEventListener(\"turbo:before-fetch-response\", this.inspectFetchResponse, false);\n }\n }\n stop() {\n if (this.started) {\n this.started = false;\n removeEventListener(\"turbo:before-fetch-response\", this.inspectFetchResponse, false);\n }\n }\n connectStreamSource(source) {\n if (!this.streamSourceIsConnected(source)) {\n this.sources.add(source);\n source.addEventListener(\"message\", this.receiveMessageEvent, false);\n }\n }\n disconnectStreamSource(source) {\n if (this.streamSourceIsConnected(source)) {\n this.sources.delete(source);\n source.removeEventListener(\"message\", this.receiveMessageEvent, false);\n }\n }\n streamSourceIsConnected(source) {\n return this.sources.has(source);\n }\n async receiveMessageResponse(response) {\n const html = await response.responseHTML;\n if (html) {\n this.receiveMessageHTML(html);\n }\n }\n receiveMessageHTML(html) {\n this.delegate.receivedMessageFromStream(StreamMessage.wrap(html));\n }\n}\nfunction fetchResponseFromEvent(event) {\n var _a;\n const fetchResponse = (_a = event.detail) === null || _a === void 0 ? void 0 : _a.fetchResponse;\n if (fetchResponse instanceof FetchResponse) {\n return fetchResponse;\n }\n}\nfunction fetchResponseIsStream(response) {\n var _a;\n const contentType = (_a = response.contentType) !== null && _a !== void 0 ? _a : \"\";\n return contentType.startsWith(StreamMessage.contentType);\n}\n\nclass ErrorRenderer extends Renderer {\n static renderElement(currentElement, newElement) {\n const { documentElement, body } = document;\n documentElement.replaceChild(newElement, body);\n }\n async render() {\n this.replaceHeadAndBody();\n this.activateScriptElements();\n }\n replaceHeadAndBody() {\n const { documentElement, head } = document;\n documentElement.replaceChild(this.newHead, head);\n this.renderElement(this.currentElement, this.newElement);\n }\n activateScriptElements() {\n for (const replaceableElement of this.scriptElements) {\n const parentNode = replaceableElement.parentNode;\n if (parentNode) {\n const element = activateScriptElement(replaceableElement);\n parentNode.replaceChild(element, replaceableElement);\n }\n }\n }\n get newHead() {\n return this.newSnapshot.headSnapshot.element;\n }\n get scriptElements() {\n return document.documentElement.querySelectorAll(\"script\");\n }\n}\n\nclass PageRenderer extends Renderer {\n static renderElement(currentElement, newElement) {\n if (document.body && newElement instanceof HTMLBodyElement) {\n document.body.replaceWith(newElement);\n }\n else {\n document.documentElement.appendChild(newElement);\n }\n }\n get shouldRender() {\n return this.newSnapshot.isVisitable && this.trackedElementsAreIdentical;\n }\n get reloadReason() {\n if (!this.newSnapshot.isVisitable) {\n return {\n reason: \"turbo_visit_control_is_reload\",\n };\n }\n if (!this.trackedElementsAreIdentical) {\n return {\n reason: \"tracked_element_mismatch\",\n };\n }\n }\n async prepareToRender() {\n await this.mergeHead();\n }\n async render() {\n if (this.willRender) {\n await this.replaceBody();\n }\n }\n finishRendering() {\n super.finishRendering();\n if (!this.isPreview) {\n this.focusFirstAutofocusableElement();\n }\n }\n get currentHeadSnapshot() {\n return this.currentSnapshot.headSnapshot;\n }\n get newHeadSnapshot() {\n return this.newSnapshot.headSnapshot;\n }\n get newElement() {\n return this.newSnapshot.element;\n }\n async mergeHead() {\n const mergedHeadElements = this.mergeProvisionalElements();\n const newStylesheetElements = this.copyNewHeadStylesheetElements();\n this.copyNewHeadScriptElements();\n await mergedHeadElements;\n await newStylesheetElements;\n }\n async replaceBody() {\n await this.preservingPermanentElements(async () => {\n this.activateNewBody();\n await this.assignNewBody();\n });\n }\n get trackedElementsAreIdentical() {\n return this.currentHeadSnapshot.trackedElementSignature == this.newHeadSnapshot.trackedElementSignature;\n }\n async copyNewHeadStylesheetElements() {\n const loadingElements = [];\n for (const element of this.newHeadStylesheetElements) {\n loadingElements.push(waitForLoad(element));\n document.head.appendChild(element);\n }\n await Promise.all(loadingElements);\n }\n copyNewHeadScriptElements() {\n for (const element of this.newHeadScriptElements) {\n document.head.appendChild(activateScriptElement(element));\n }\n }\n async mergeProvisionalElements() {\n const newHeadElements = [...this.newHeadProvisionalElements];\n for (const element of this.currentHeadProvisionalElements) {\n if (!this.isCurrentElementInElementList(element, newHeadElements)) {\n document.head.removeChild(element);\n }\n }\n for (const element of newHeadElements) {\n document.head.appendChild(element);\n }\n }\n isCurrentElementInElementList(element, elementList) {\n for (const [index, newElement] of elementList.entries()) {\n if (element.tagName == \"TITLE\") {\n if (newElement.tagName != \"TITLE\") {\n continue;\n }\n if (element.innerHTML == newElement.innerHTML) {\n elementList.splice(index, 1);\n return true;\n }\n }\n if (newElement.isEqualNode(element)) {\n elementList.splice(index, 1);\n return true;\n }\n }\n return false;\n }\n removeCurrentHeadProvisionalElements() {\n for (const element of this.currentHeadProvisionalElements) {\n document.head.removeChild(element);\n }\n }\n copyNewHeadProvisionalElements() {\n for (const element of this.newHeadProvisionalElements) {\n document.head.appendChild(element);\n }\n }\n activateNewBody() {\n document.adoptNode(this.newElement);\n this.activateNewBodyScriptElements();\n }\n activateNewBodyScriptElements() {\n for (const inertScriptElement of this.newBodyScriptElements) {\n const activatedScriptElement = activateScriptElement(inertScriptElement);\n inertScriptElement.replaceWith(activatedScriptElement);\n }\n }\n async assignNewBody() {\n await this.renderElement(this.currentElement, this.newElement);\n }\n get newHeadStylesheetElements() {\n return this.newHeadSnapshot.getStylesheetElementsNotInSnapshot(this.currentHeadSnapshot);\n }\n get newHeadScriptElements() {\n return this.newHeadSnapshot.getScriptElementsNotInSnapshot(this.currentHeadSnapshot);\n }\n get currentHeadProvisionalElements() {\n return this.currentHeadSnapshot.provisionalElements;\n }\n get newHeadProvisionalElements() {\n return this.newHeadSnapshot.provisionalElements;\n }\n get newBodyScriptElements() {\n return this.newElement.querySelectorAll(\"script\");\n }\n}\n\nclass SnapshotCache {\n constructor(size) {\n this.keys = [];\n this.snapshots = {};\n this.size = size;\n }\n has(location) {\n return toCacheKey(location) in this.snapshots;\n }\n get(location) {\n if (this.has(location)) {\n const snapshot = this.read(location);\n this.touch(location);\n return snapshot;\n }\n }\n put(location, snapshot) {\n this.write(location, snapshot);\n this.touch(location);\n return snapshot;\n }\n clear() {\n this.snapshots = {};\n }\n read(location) {\n return this.snapshots[toCacheKey(location)];\n }\n write(location, snapshot) {\n this.snapshots[toCacheKey(location)] = snapshot;\n }\n touch(location) {\n const key = toCacheKey(location);\n const index = this.keys.indexOf(key);\n if (index > -1)\n this.keys.splice(index, 1);\n this.keys.unshift(key);\n this.trim();\n }\n trim() {\n for (const key of this.keys.splice(this.size)) {\n delete this.snapshots[key];\n }\n }\n}\n\nclass PageView extends View {\n constructor() {\n super(...arguments);\n this.snapshotCache = new SnapshotCache(10);\n this.lastRenderedLocation = new URL(location.href);\n this.forceReloaded = false;\n }\n renderPage(snapshot, isPreview = false, willRender = true, visit) {\n const renderer = new PageRenderer(this.snapshot, snapshot, PageRenderer.renderElement, isPreview, willRender);\n if (!renderer.shouldRender) {\n this.forceReloaded = true;\n }\n else {\n visit === null || visit === void 0 ? void 0 : visit.changeHistory();\n }\n return this.render(renderer);\n }\n renderError(snapshot, visit) {\n visit === null || visit === void 0 ? void 0 : visit.changeHistory();\n const renderer = new ErrorRenderer(this.snapshot, snapshot, ErrorRenderer.renderElement, false);\n return this.render(renderer);\n }\n clearSnapshotCache() {\n this.snapshotCache.clear();\n }\n async cacheSnapshot(snapshot = this.snapshot) {\n if (snapshot.isCacheable) {\n this.delegate.viewWillCacheSnapshot();\n const { lastRenderedLocation: location } = this;\n await nextEventLoopTick();\n const cachedSnapshot = snapshot.clone();\n this.snapshotCache.put(location, cachedSnapshot);\n return cachedSnapshot;\n }\n }\n getCachedSnapshotForLocation(location) {\n return this.snapshotCache.get(location);\n }\n get snapshot() {\n return PageSnapshot.fromElement(this.element);\n }\n}\n\nclass Preloader {\n constructor(delegate) {\n this.selector = \"a[data-turbo-preload]\";\n this.delegate = delegate;\n }\n get snapshotCache() {\n return this.delegate.navigator.view.snapshotCache;\n }\n start() {\n if (document.readyState === \"loading\") {\n return document.addEventListener(\"DOMContentLoaded\", () => {\n this.preloadOnLoadLinksForView(document.body);\n });\n }\n else {\n this.preloadOnLoadLinksForView(document.body);\n }\n }\n preloadOnLoadLinksForView(element) {\n for (const link of element.querySelectorAll(this.selector)) {\n this.preloadURL(link);\n }\n }\n async preloadURL(link) {\n const location = new URL(link.href);\n if (this.snapshotCache.has(location)) {\n return;\n }\n try {\n const response = await fetch(location.toString(), { headers: { \"VND.PREFETCH\": \"true\", Accept: \"text/html\" } });\n const responseText = await response.text();\n const snapshot = PageSnapshot.fromHTMLString(responseText);\n this.snapshotCache.put(location, snapshot);\n }\n catch (_) {\n }\n }\n}\n\nclass Session {\n constructor() {\n this.navigator = new Navigator(this);\n this.history = new History(this);\n this.preloader = new Preloader(this);\n this.view = new PageView(this, document.documentElement);\n this.adapter = new BrowserAdapter(this);\n this.pageObserver = new PageObserver(this);\n this.cacheObserver = new CacheObserver();\n this.linkClickObserver = new LinkClickObserver(this, window);\n this.formSubmitObserver = new FormSubmitObserver(this, document);\n this.scrollObserver = new ScrollObserver(this);\n this.streamObserver = new StreamObserver(this);\n this.formLinkClickObserver = new FormLinkClickObserver(this, document.documentElement);\n this.frameRedirector = new FrameRedirector(this, document.documentElement);\n this.streamMessageRenderer = new StreamMessageRenderer();\n this.drive = true;\n this.enabled = true;\n this.progressBarDelay = 500;\n this.started = false;\n this.formMode = \"on\";\n }\n start() {\n if (!this.started) {\n this.pageObserver.start();\n this.cacheObserver.start();\n this.formLinkClickObserver.start();\n this.linkClickObserver.start();\n this.formSubmitObserver.start();\n this.scrollObserver.start();\n this.streamObserver.start();\n this.frameRedirector.start();\n this.history.start();\n this.preloader.start();\n this.started = true;\n this.enabled = true;\n }\n }\n disable() {\n this.enabled = false;\n }\n stop() {\n if (this.started) {\n this.pageObserver.stop();\n this.cacheObserver.stop();\n this.formLinkClickObserver.stop();\n this.linkClickObserver.stop();\n this.formSubmitObserver.stop();\n this.scrollObserver.stop();\n this.streamObserver.stop();\n this.frameRedirector.stop();\n this.history.stop();\n this.started = false;\n }\n }\n registerAdapter(adapter) {\n this.adapter = adapter;\n }\n visit(location, options = {}) {\n const frameElement = options.frame ? document.getElementById(options.frame) : null;\n if (frameElement instanceof FrameElement) {\n frameElement.src = location.toString();\n frameElement.loaded;\n }\n else {\n this.navigator.proposeVisit(expandURL(location), options);\n }\n }\n connectStreamSource(source) {\n this.streamObserver.connectStreamSource(source);\n }\n disconnectStreamSource(source) {\n this.streamObserver.disconnectStreamSource(source);\n }\n renderStreamMessage(message) {\n this.streamMessageRenderer.render(StreamMessage.wrap(message));\n }\n clearCache() {\n this.view.clearSnapshotCache();\n }\n setProgressBarDelay(delay) {\n this.progressBarDelay = delay;\n }\n setFormMode(mode) {\n this.formMode = mode;\n }\n get location() {\n return this.history.location;\n }\n get restorationIdentifier() {\n return this.history.restorationIdentifier;\n }\n historyPoppedToLocationWithRestorationIdentifier(location, restorationIdentifier) {\n if (this.enabled) {\n this.navigator.startVisit(location, restorationIdentifier, {\n action: \"restore\",\n historyChanged: true,\n });\n }\n else {\n this.adapter.pageInvalidated({\n reason: \"turbo_disabled\",\n });\n }\n }\n scrollPositionChanged(position) {\n this.history.updateRestorationData({ scrollPosition: position });\n }\n willSubmitFormLinkToLocation(link, location) {\n return this.elementIsNavigatable(link) && locationIsVisitable(location, this.snapshot.rootLocation);\n }\n submittedFormLinkToLocation() { }\n willFollowLinkToLocation(link, location, event) {\n return (this.elementIsNavigatable(link) &&\n locationIsVisitable(location, this.snapshot.rootLocation) &&\n this.applicationAllowsFollowingLinkToLocation(link, location, event));\n }\n followedLinkToLocation(link, location) {\n const action = this.getActionForLink(link);\n const acceptsStreamResponse = link.hasAttribute(\"data-turbo-stream\");\n this.visit(location.href, { action, acceptsStreamResponse });\n }\n allowsVisitingLocationWithAction(location, action) {\n return this.locationWithActionIsSamePage(location, action) || this.applicationAllowsVisitingLocation(location);\n }\n visitProposedToLocation(location, options) {\n extendURLWithDeprecatedProperties(location);\n this.adapter.visitProposedToLocation(location, options);\n }\n visitStarted(visit) {\n if (!visit.acceptsStreamResponse) {\n markAsBusy(document.documentElement);\n }\n extendURLWithDeprecatedProperties(visit.location);\n if (!visit.silent) {\n this.notifyApplicationAfterVisitingLocation(visit.location, visit.action);\n }\n }\n visitCompleted(visit) {\n clearBusyState(document.documentElement);\n this.notifyApplicationAfterPageLoad(visit.getTimingMetrics());\n }\n locationWithActionIsSamePage(location, action) {\n return this.navigator.locationWithActionIsSamePage(location, action);\n }\n visitScrolledToSamePageLocation(oldURL, newURL) {\n this.notifyApplicationAfterVisitingSamePageLocation(oldURL, newURL);\n }\n willSubmitForm(form, submitter) {\n const action = getAction(form, submitter);\n return (this.submissionIsNavigatable(form, submitter) &&\n locationIsVisitable(expandURL(action), this.snapshot.rootLocation));\n }\n formSubmitted(form, submitter) {\n this.navigator.submitForm(form, submitter);\n }\n pageBecameInteractive() {\n this.view.lastRenderedLocation = this.location;\n this.notifyApplicationAfterPageLoad();\n }\n pageLoaded() {\n this.history.assumeControlOfScrollRestoration();\n }\n pageWillUnload() {\n this.history.relinquishControlOfScrollRestoration();\n }\n receivedMessageFromStream(message) {\n this.renderStreamMessage(message);\n }\n viewWillCacheSnapshot() {\n var _a;\n if (!((_a = this.navigator.currentVisit) === null || _a === void 0 ? void 0 : _a.silent)) {\n this.notifyApplicationBeforeCachingSnapshot();\n }\n }\n allowsImmediateRender({ element }, options) {\n const event = this.notifyApplicationBeforeRender(element, options);\n const { defaultPrevented, detail: { render }, } = event;\n if (this.view.renderer && render) {\n this.view.renderer.renderElement = render;\n }\n return !defaultPrevented;\n }\n viewRenderedSnapshot(_snapshot, _isPreview) {\n this.view.lastRenderedLocation = this.history.location;\n this.notifyApplicationAfterRender();\n }\n preloadOnLoadLinksForView(element) {\n this.preloader.preloadOnLoadLinksForView(element);\n }\n viewInvalidated(reason) {\n this.adapter.pageInvalidated(reason);\n }\n frameLoaded(frame) {\n this.notifyApplicationAfterFrameLoad(frame);\n }\n frameRendered(fetchResponse, frame) {\n this.notifyApplicationAfterFrameRender(fetchResponse, frame);\n }\n applicationAllowsFollowingLinkToLocation(link, location, ev) {\n const event = this.notifyApplicationAfterClickingLinkToLocation(link, location, ev);\n return !event.defaultPrevented;\n }\n applicationAllowsVisitingLocation(location) {\n const event = this.notifyApplicationBeforeVisitingLocation(location);\n return !event.defaultPrevented;\n }\n notifyApplicationAfterClickingLinkToLocation(link, location, event) {\n return dispatch(\"turbo:click\", {\n target: link,\n detail: { url: location.href, originalEvent: event },\n cancelable: true,\n });\n }\n notifyApplicationBeforeVisitingLocation(location) {\n return dispatch(\"turbo:before-visit\", {\n detail: { url: location.href },\n cancelable: true,\n });\n }\n notifyApplicationAfterVisitingLocation(location, action) {\n return dispatch(\"turbo:visit\", { detail: { url: location.href, action } });\n }\n notifyApplicationBeforeCachingSnapshot() {\n return dispatch(\"turbo:before-cache\");\n }\n notifyApplicationBeforeRender(newBody, options) {\n return dispatch(\"turbo:before-render\", {\n detail: Object.assign({ newBody }, options),\n cancelable: true,\n });\n }\n notifyApplicationAfterRender() {\n return dispatch(\"turbo:render\");\n }\n notifyApplicationAfterPageLoad(timing = {}) {\n return dispatch(\"turbo:load\", {\n detail: { url: this.location.href, timing },\n });\n }\n notifyApplicationAfterVisitingSamePageLocation(oldURL, newURL) {\n dispatchEvent(new HashChangeEvent(\"hashchange\", {\n oldURL: oldURL.toString(),\n newURL: newURL.toString(),\n }));\n }\n notifyApplicationAfterFrameLoad(frame) {\n return dispatch(\"turbo:frame-load\", { target: frame });\n }\n notifyApplicationAfterFrameRender(fetchResponse, frame) {\n return dispatch(\"turbo:frame-render\", {\n detail: { fetchResponse },\n target: frame,\n cancelable: true,\n });\n }\n submissionIsNavigatable(form, submitter) {\n if (this.formMode == \"off\") {\n return false;\n }\n else {\n const submitterIsNavigatable = submitter ? this.elementIsNavigatable(submitter) : true;\n if (this.formMode == \"optin\") {\n return submitterIsNavigatable && form.closest('[data-turbo=\"true\"]') != null;\n }\n else {\n return submitterIsNavigatable && this.elementIsNavigatable(form);\n }\n }\n }\n elementIsNavigatable(element) {\n const container = findClosestRecursively(element, \"[data-turbo]\");\n const withinFrame = findClosestRecursively(element, \"turbo-frame\");\n if (this.drive || withinFrame) {\n if (container) {\n return container.getAttribute(\"data-turbo\") != \"false\";\n }\n else {\n return true;\n }\n }\n else {\n if (container) {\n return container.getAttribute(\"data-turbo\") == \"true\";\n }\n else {\n return false;\n }\n }\n }\n getActionForLink(link) {\n return getVisitAction(link) || \"advance\";\n }\n get snapshot() {\n return this.view.snapshot;\n }\n}\nfunction extendURLWithDeprecatedProperties(url) {\n Object.defineProperties(url, deprecatedLocationPropertyDescriptors);\n}\nconst deprecatedLocationPropertyDescriptors = {\n absoluteURL: {\n get() {\n return this.toString();\n },\n },\n};\n\nclass Cache {\n constructor(session) {\n this.session = session;\n }\n clear() {\n this.session.clearCache();\n }\n resetCacheControl() {\n this.setCacheControl(\"\");\n }\n exemptPageFromCache() {\n this.setCacheControl(\"no-cache\");\n }\n exemptPageFromPreview() {\n this.setCacheControl(\"no-preview\");\n }\n setCacheControl(value) {\n setMetaContent(\"turbo-cache-control\", value);\n }\n}\n\nconst StreamActions = {\n after() {\n this.targetElements.forEach((e) => { var _a; return (_a = e.parentElement) === null || _a === void 0 ? void 0 : _a.insertBefore(this.templateContent, e.nextSibling); });\n },\n append() {\n this.removeDuplicateTargetChildren();\n this.targetElements.forEach((e) => e.append(this.templateContent));\n },\n before() {\n this.targetElements.forEach((e) => { var _a; return (_a = e.parentElement) === null || _a === void 0 ? void 0 : _a.insertBefore(this.templateContent, e); });\n },\n prepend() {\n this.removeDuplicateTargetChildren();\n this.targetElements.forEach((e) => e.prepend(this.templateContent));\n },\n remove() {\n this.targetElements.forEach((e) => e.remove());\n },\n replace() {\n this.targetElements.forEach((e) => e.replaceWith(this.templateContent));\n },\n update() {\n this.targetElements.forEach((targetElement) => {\n targetElement.innerHTML = \"\";\n targetElement.append(this.templateContent);\n });\n },\n};\n\nconst session = new Session();\nconst cache = new Cache(session);\nconst { navigator: navigator$1 } = session;\nfunction start() {\n session.start();\n}\nfunction registerAdapter(adapter) {\n session.registerAdapter(adapter);\n}\nfunction visit(location, options) {\n session.visit(location, options);\n}\nfunction connectStreamSource(source) {\n session.connectStreamSource(source);\n}\nfunction disconnectStreamSource(source) {\n session.disconnectStreamSource(source);\n}\nfunction renderStreamMessage(message) {\n session.renderStreamMessage(message);\n}\nfunction clearCache() {\n console.warn(\"Please replace `Turbo.clearCache()` with `Turbo.cache.clear()`. The top-level function is deprecated and will be removed in a future version of Turbo.`\");\n session.clearCache();\n}\nfunction setProgressBarDelay(delay) {\n session.setProgressBarDelay(delay);\n}\nfunction setConfirmMethod(confirmMethod) {\n FormSubmission.confirmMethod = confirmMethod;\n}\nfunction setFormMode(mode) {\n session.setFormMode(mode);\n}\n\nvar Turbo = /*#__PURE__*/Object.freeze({\n __proto__: null,\n navigator: navigator$1,\n session: session,\n cache: cache,\n PageRenderer: PageRenderer,\n PageSnapshot: PageSnapshot,\n FrameRenderer: FrameRenderer,\n start: start,\n registerAdapter: registerAdapter,\n visit: visit,\n connectStreamSource: connectStreamSource,\n disconnectStreamSource: disconnectStreamSource,\n renderStreamMessage: renderStreamMessage,\n clearCache: clearCache,\n setProgressBarDelay: setProgressBarDelay,\n setConfirmMethod: setConfirmMethod,\n setFormMode: setFormMode,\n StreamActions: StreamActions\n});\n\nclass TurboFrameMissingError extends Error {\n}\n\nclass FrameController {\n constructor(element) {\n this.fetchResponseLoaded = (_fetchResponse) => { };\n this.currentFetchRequest = null;\n this.resolveVisitPromise = () => { };\n this.connected = false;\n this.hasBeenLoaded = false;\n this.ignoredAttributes = new Set();\n this.action = null;\n this.visitCachedSnapshot = ({ element }) => {\n const frame = element.querySelector(\"#\" + this.element.id);\n if (frame && this.previousFrameElement) {\n frame.replaceChildren(...this.previousFrameElement.children);\n }\n delete this.previousFrameElement;\n };\n this.element = element;\n this.view = new FrameView(this, this.element);\n this.appearanceObserver = new AppearanceObserver(this, this.element);\n this.formLinkClickObserver = new FormLinkClickObserver(this, this.element);\n this.linkInterceptor = new LinkInterceptor(this, this.element);\n this.restorationIdentifier = uuid();\n this.formSubmitObserver = new FormSubmitObserver(this, this.element);\n }\n connect() {\n if (!this.connected) {\n this.connected = true;\n if (this.loadingStyle == FrameLoadingStyle.lazy) {\n this.appearanceObserver.start();\n }\n else {\n this.loadSourceURL();\n }\n this.formLinkClickObserver.start();\n this.linkInterceptor.start();\n this.formSubmitObserver.start();\n }\n }\n disconnect() {\n if (this.connected) {\n this.connected = false;\n this.appearanceObserver.stop();\n this.formLinkClickObserver.stop();\n this.linkInterceptor.stop();\n this.formSubmitObserver.stop();\n }\n }\n disabledChanged() {\n if (this.loadingStyle == FrameLoadingStyle.eager) {\n this.loadSourceURL();\n }\n }\n sourceURLChanged() {\n if (this.isIgnoringChangesTo(\"src\"))\n return;\n if (this.element.isConnected) {\n this.complete = false;\n }\n if (this.loadingStyle == FrameLoadingStyle.eager || this.hasBeenLoaded) {\n this.loadSourceURL();\n }\n }\n sourceURLReloaded() {\n const { src } = this.element;\n this.ignoringChangesToAttribute(\"complete\", () => {\n this.element.removeAttribute(\"complete\");\n });\n this.element.src = null;\n this.element.src = src;\n return this.element.loaded;\n }\n completeChanged() {\n if (this.isIgnoringChangesTo(\"complete\"))\n return;\n this.loadSourceURL();\n }\n loadingStyleChanged() {\n if (this.loadingStyle == FrameLoadingStyle.lazy) {\n this.appearanceObserver.start();\n }\n else {\n this.appearanceObserver.stop();\n this.loadSourceURL();\n }\n }\n async loadSourceURL() {\n if (this.enabled && this.isActive && !this.complete && this.sourceURL) {\n this.element.loaded = this.visit(expandURL(this.sourceURL));\n this.appearanceObserver.stop();\n await this.element.loaded;\n this.hasBeenLoaded = true;\n }\n }\n async loadResponse(fetchResponse) {\n if (fetchResponse.redirected || (fetchResponse.succeeded && fetchResponse.isHTML)) {\n this.sourceURL = fetchResponse.response.url;\n }\n try {\n const html = await fetchResponse.responseHTML;\n if (html) {\n const document = parseHTMLDocument(html);\n const pageSnapshot = PageSnapshot.fromDocument(document);\n if (pageSnapshot.isVisitable) {\n await this.loadFrameResponse(fetchResponse, document);\n }\n else {\n await this.handleUnvisitableFrameResponse(fetchResponse);\n }\n }\n }\n finally {\n this.fetchResponseLoaded = () => { };\n }\n }\n elementAppearedInViewport(element) {\n this.proposeVisitIfNavigatedWithAction(element, element);\n this.loadSourceURL();\n }\n willSubmitFormLinkToLocation(link) {\n return this.shouldInterceptNavigation(link);\n }\n submittedFormLinkToLocation(link, _location, form) {\n const frame = this.findFrameElement(link);\n if (frame)\n form.setAttribute(\"data-turbo-frame\", frame.id);\n }\n shouldInterceptLinkClick(element, _location, _event) {\n return this.shouldInterceptNavigation(element);\n }\n linkClickIntercepted(element, location) {\n this.navigateFrame(element, location);\n }\n willSubmitForm(element, submitter) {\n return element.closest(\"turbo-frame\") == this.element && this.shouldInterceptNavigation(element, submitter);\n }\n formSubmitted(element, submitter) {\n if (this.formSubmission) {\n this.formSubmission.stop();\n }\n this.formSubmission = new FormSubmission(this, element, submitter);\n const { fetchRequest } = this.formSubmission;\n this.prepareRequest(fetchRequest);\n this.formSubmission.start();\n }\n prepareRequest(request) {\n var _a;\n request.headers[\"Turbo-Frame\"] = this.id;\n if ((_a = this.currentNavigationElement) === null || _a === void 0 ? void 0 : _a.hasAttribute(\"data-turbo-stream\")) {\n request.acceptResponseType(StreamMessage.contentType);\n }\n }\n requestStarted(_request) {\n markAsBusy(this.element);\n }\n requestPreventedHandlingResponse(_request, _response) {\n this.resolveVisitPromise();\n }\n async requestSucceededWithResponse(request, response) {\n await this.loadResponse(response);\n this.resolveVisitPromise();\n }\n async requestFailedWithResponse(request, response) {\n await this.loadResponse(response);\n this.resolveVisitPromise();\n }\n requestErrored(request, error) {\n console.error(error);\n this.resolveVisitPromise();\n }\n requestFinished(_request) {\n clearBusyState(this.element);\n }\n formSubmissionStarted({ formElement }) {\n markAsBusy(formElement, this.findFrameElement(formElement));\n }\n formSubmissionSucceededWithResponse(formSubmission, response) {\n const frame = this.findFrameElement(formSubmission.formElement, formSubmission.submitter);\n frame.delegate.proposeVisitIfNavigatedWithAction(frame, formSubmission.formElement, formSubmission.submitter);\n frame.delegate.loadResponse(response);\n if (!formSubmission.isSafe) {\n session.clearCache();\n }\n }\n formSubmissionFailedWithResponse(formSubmission, fetchResponse) {\n this.element.delegate.loadResponse(fetchResponse);\n session.clearCache();\n }\n formSubmissionErrored(formSubmission, error) {\n console.error(error);\n }\n formSubmissionFinished({ formElement }) {\n clearBusyState(formElement, this.findFrameElement(formElement));\n }\n allowsImmediateRender({ element: newFrame }, options) {\n const event = dispatch(\"turbo:before-frame-render\", {\n target: this.element,\n detail: Object.assign({ newFrame }, options),\n cancelable: true,\n });\n const { defaultPrevented, detail: { render }, } = event;\n if (this.view.renderer && render) {\n this.view.renderer.renderElement = render;\n }\n return !defaultPrevented;\n }\n viewRenderedSnapshot(_snapshot, _isPreview) { }\n preloadOnLoadLinksForView(element) {\n session.preloadOnLoadLinksForView(element);\n }\n viewInvalidated() { }\n willRenderFrame(currentElement, _newElement) {\n this.previousFrameElement = currentElement.cloneNode(true);\n }\n async loadFrameResponse(fetchResponse, document) {\n const newFrameElement = await this.extractForeignFrameElement(document.body);\n if (newFrameElement) {\n const snapshot = new Snapshot(newFrameElement);\n const renderer = new FrameRenderer(this, this.view.snapshot, snapshot, FrameRenderer.renderElement, false, false);\n if (this.view.renderPromise)\n await this.view.renderPromise;\n this.changeHistory();\n await this.view.render(renderer);\n this.complete = true;\n session.frameRendered(fetchResponse, this.element);\n session.frameLoaded(this.element);\n this.fetchResponseLoaded(fetchResponse);\n }\n else if (this.willHandleFrameMissingFromResponse(fetchResponse)) {\n this.handleFrameMissingFromResponse(fetchResponse);\n }\n }\n async visit(url) {\n var _a;\n const request = new FetchRequest(this, FetchMethod.get, url, new URLSearchParams(), this.element);\n (_a = this.currentFetchRequest) === null || _a === void 0 ? void 0 : _a.cancel();\n this.currentFetchRequest = request;\n return new Promise((resolve) => {\n this.resolveVisitPromise = () => {\n this.resolveVisitPromise = () => { };\n this.currentFetchRequest = null;\n resolve();\n };\n request.perform();\n });\n }\n navigateFrame(element, url, submitter) {\n const frame = this.findFrameElement(element, submitter);\n frame.delegate.proposeVisitIfNavigatedWithAction(frame, element, submitter);\n this.withCurrentNavigationElement(element, () => {\n frame.src = url;\n });\n }\n proposeVisitIfNavigatedWithAction(frame, element, submitter) {\n this.action = getVisitAction(submitter, element, frame);\n if (this.action) {\n const pageSnapshot = PageSnapshot.fromElement(frame).clone();\n const { visitCachedSnapshot } = frame.delegate;\n frame.delegate.fetchResponseLoaded = (fetchResponse) => {\n if (frame.src) {\n const { statusCode, redirected } = fetchResponse;\n const responseHTML = frame.ownerDocument.documentElement.outerHTML;\n const response = { statusCode, redirected, responseHTML };\n const options = {\n response,\n visitCachedSnapshot,\n willRender: false,\n updateHistory: false,\n restorationIdentifier: this.restorationIdentifier,\n snapshot: pageSnapshot,\n };\n if (this.action)\n options.action = this.action;\n session.visit(frame.src, options);\n }\n };\n }\n }\n changeHistory() {\n if (this.action) {\n const method = getHistoryMethodForAction(this.action);\n session.history.update(method, expandURL(this.element.src || \"\"), this.restorationIdentifier);\n }\n }\n async handleUnvisitableFrameResponse(fetchResponse) {\n console.warn(`The response (${fetchResponse.statusCode}) from is performing a full page visit due to turbo-visit-control.`);\n await this.visitResponse(fetchResponse.response);\n }\n willHandleFrameMissingFromResponse(fetchResponse) {\n this.element.setAttribute(\"complete\", \"\");\n const response = fetchResponse.response;\n const visit = async (url, options = {}) => {\n if (url instanceof Response) {\n this.visitResponse(url);\n }\n else {\n session.visit(url, options);\n }\n };\n const event = dispatch(\"turbo:frame-missing\", {\n target: this.element,\n detail: { response, visit },\n cancelable: true,\n });\n return !event.defaultPrevented;\n }\n handleFrameMissingFromResponse(fetchResponse) {\n this.view.missing();\n this.throwFrameMissingError(fetchResponse);\n }\n throwFrameMissingError(fetchResponse) {\n const message = `The response (${fetchResponse.statusCode}) did not contain the expected and will be ignored. To perform a full page visit instead, set turbo-visit-control to reload.`;\n throw new TurboFrameMissingError(message);\n }\n async visitResponse(response) {\n const wrapped = new FetchResponse(response);\n const responseHTML = await wrapped.responseHTML;\n const { location, redirected, statusCode } = wrapped;\n return session.visit(location, { response: { redirected, statusCode, responseHTML } });\n }\n findFrameElement(element, submitter) {\n var _a;\n const id = getAttribute(\"data-turbo-frame\", submitter, element) || this.element.getAttribute(\"target\");\n return (_a = getFrameElementById(id)) !== null && _a !== void 0 ? _a : this.element;\n }\n async extractForeignFrameElement(container) {\n let element;\n const id = CSS.escape(this.id);\n try {\n element = activateElement(container.querySelector(`turbo-frame#${id}`), this.sourceURL);\n if (element) {\n return element;\n }\n element = activateElement(container.querySelector(`turbo-frame[src][recurse~=${id}]`), this.sourceURL);\n if (element) {\n await element.loaded;\n return await this.extractForeignFrameElement(element);\n }\n }\n catch (error) {\n console.error(error);\n return new FrameElement();\n }\n return null;\n }\n formActionIsVisitable(form, submitter) {\n const action = getAction(form, submitter);\n return locationIsVisitable(expandURL(action), this.rootLocation);\n }\n shouldInterceptNavigation(element, submitter) {\n const id = getAttribute(\"data-turbo-frame\", submitter, element) || this.element.getAttribute(\"target\");\n if (element instanceof HTMLFormElement && !this.formActionIsVisitable(element, submitter)) {\n return false;\n }\n if (!this.enabled || id == \"_top\") {\n return false;\n }\n if (id) {\n const frameElement = getFrameElementById(id);\n if (frameElement) {\n return !frameElement.disabled;\n }\n }\n if (!session.elementIsNavigatable(element)) {\n return false;\n }\n if (submitter && !session.elementIsNavigatable(submitter)) {\n return false;\n }\n return true;\n }\n get id() {\n return this.element.id;\n }\n get enabled() {\n return !this.element.disabled;\n }\n get sourceURL() {\n if (this.element.src) {\n return this.element.src;\n }\n }\n set sourceURL(sourceURL) {\n this.ignoringChangesToAttribute(\"src\", () => {\n this.element.src = sourceURL !== null && sourceURL !== void 0 ? sourceURL : null;\n });\n }\n get loadingStyle() {\n return this.element.loading;\n }\n get isLoading() {\n return this.formSubmission !== undefined || this.resolveVisitPromise() !== undefined;\n }\n get complete() {\n return this.element.hasAttribute(\"complete\");\n }\n set complete(value) {\n this.ignoringChangesToAttribute(\"complete\", () => {\n if (value) {\n this.element.setAttribute(\"complete\", \"\");\n }\n else {\n this.element.removeAttribute(\"complete\");\n }\n });\n }\n get isActive() {\n return this.element.isActive && this.connected;\n }\n get rootLocation() {\n var _a;\n const meta = this.element.ownerDocument.querySelector(`meta[name=\"turbo-root\"]`);\n const root = (_a = meta === null || meta === void 0 ? void 0 : meta.content) !== null && _a !== void 0 ? _a : \"/\";\n return expandURL(root);\n }\n isIgnoringChangesTo(attributeName) {\n return this.ignoredAttributes.has(attributeName);\n }\n ignoringChangesToAttribute(attributeName, callback) {\n this.ignoredAttributes.add(attributeName);\n callback();\n this.ignoredAttributes.delete(attributeName);\n }\n withCurrentNavigationElement(element, callback) {\n this.currentNavigationElement = element;\n callback();\n delete this.currentNavigationElement;\n }\n}\nfunction getFrameElementById(id) {\n if (id != null) {\n const element = document.getElementById(id);\n if (element instanceof FrameElement) {\n return element;\n }\n }\n}\nfunction activateElement(element, currentURL) {\n if (element) {\n const src = element.getAttribute(\"src\");\n if (src != null && currentURL != null && urlsAreEqual(src, currentURL)) {\n throw new Error(`Matching element has a source URL which references itself`);\n }\n if (element.ownerDocument !== document) {\n element = document.importNode(element, true);\n }\n if (element instanceof FrameElement) {\n element.connectedCallback();\n element.disconnectedCallback();\n return element;\n }\n }\n}\n\nclass StreamElement extends HTMLElement {\n static async renderElement(newElement) {\n await newElement.performAction();\n }\n async connectedCallback() {\n try {\n await this.render();\n }\n catch (error) {\n console.error(error);\n }\n finally {\n this.disconnect();\n }\n }\n async render() {\n var _a;\n return ((_a = this.renderPromise) !== null && _a !== void 0 ? _a : (this.renderPromise = (async () => {\n const event = this.beforeRenderEvent;\n if (this.dispatchEvent(event)) {\n await nextAnimationFrame();\n await event.detail.render(this);\n }\n })()));\n }\n disconnect() {\n try {\n this.remove();\n }\n catch (_a) { }\n }\n removeDuplicateTargetChildren() {\n this.duplicateChildren.forEach((c) => c.remove());\n }\n get duplicateChildren() {\n var _a;\n const existingChildren = this.targetElements.flatMap((e) => [...e.children]).filter((c) => !!c.id);\n const newChildrenIds = [...(((_a = this.templateContent) === null || _a === void 0 ? void 0 : _a.children) || [])].filter((c) => !!c.id).map((c) => c.id);\n return existingChildren.filter((c) => newChildrenIds.includes(c.id));\n }\n get performAction() {\n if (this.action) {\n const actionFunction = StreamActions[this.action];\n if (actionFunction) {\n return actionFunction;\n }\n this.raise(\"unknown action\");\n }\n this.raise(\"action attribute is missing\");\n }\n get targetElements() {\n if (this.target) {\n return this.targetElementsById;\n }\n else if (this.targets) {\n return this.targetElementsByQuery;\n }\n else {\n this.raise(\"target or targets attribute is missing\");\n }\n }\n get templateContent() {\n return this.templateElement.content.cloneNode(true);\n }\n get templateElement() {\n if (this.firstElementChild === null) {\n const template = this.ownerDocument.createElement(\"template\");\n this.appendChild(template);\n return template;\n }\n else if (this.firstElementChild instanceof HTMLTemplateElement) {\n return this.firstElementChild;\n }\n this.raise(\"first child element must be a