├── .gitattributes ├── .gitignore ├── BrowserDetectDemo ├── App.razor ├── BrowserDetectDemo.csproj ├── Pages │ └── Index.razor ├── Program.cs ├── Properties │ └── launchSettings.json ├── Shared │ ├── MainLayout.razor │ ├── MainLayout.razor.css │ ├── NavMenu.razor │ └── NavMenu.razor.css ├── _Imports.razor └── wwwroot │ ├── css │ ├── app.css │ ├── bootstrap │ │ ├── bootstrap.min.css │ │ └── bootstrap.min.css.map │ └── open-iconic │ │ ├── FONT-LICENSE │ │ ├── ICON-LICENSE │ │ ├── README.md │ │ └── font │ │ ├── css │ │ └── open-iconic-bootstrap.min.css │ │ └── fonts │ │ ├── open-iconic.eot │ │ ├── open-iconic.otf │ │ ├── open-iconic.svg │ │ ├── open-iconic.ttf │ │ └── open-iconic.woff │ ├── favicon.ico │ ├── icon-192.png │ ├── icon-512.png │ ├── images │ └── psc_logo.png │ ├── index.html │ ├── manifest.json │ ├── service-worker.js │ └── service-worker.published.js ├── PSC.Blazor.Components.BrowserDetect.sln ├── README.md └── src ├── BrowserDetect.razor ├── BrowserDetectJsInterop.cs ├── Models └── BrowserDetect.cs ├── PSC.Blazor.Components.BrowserDetect.csproj ├── _Imports.razor ├── psc_logo.ico ├── psc_logo.png └── wwwroot └── js └── browserDetect.js /.gitattributes: -------------------------------------------------------------------------------- 1 | *.razor linguist-language=C# 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Ww][Ii][Nn]32/ 27 | [Aa][Rr][Mm]/ 28 | [Aa][Rr][Mm]64/ 29 | bld/ 30 | [Bb]in/ 31 | [Oo]bj/ 32 | [Ll]og/ 33 | [Ll]ogs/ 34 | 35 | # Visual Studio 2015/2017 cache/options directory 36 | .vs/ 37 | # Uncomment if you have tasks that create the project's static files in wwwroot 38 | #wwwroot/ 39 | 40 | # Visual Studio 2017 auto generated files 41 | Generated\ Files/ 42 | 43 | # MSTest test Results 44 | [Tt]est[Rr]esult*/ 45 | [Bb]uild[Ll]og.* 46 | 47 | # NUnit 48 | *.VisualState.xml 49 | TestResult.xml 50 | nunit-*.xml 51 | 52 | # Build Results of an ATL Project 53 | [Dd]ebugPS/ 54 | [Rr]eleasePS/ 55 | dlldata.c 56 | 57 | # Benchmark Results 58 | BenchmarkDotNet.Artifacts/ 59 | 60 | # .NET Core 61 | project.lock.json 62 | project.fragment.lock.json 63 | artifacts/ 64 | 65 | # ASP.NET Scaffolding 66 | ScaffoldingReadMe.txt 67 | 68 | # StyleCop 69 | StyleCopReport.xml 70 | 71 | # Files built by Visual Studio 72 | *_i.c 73 | *_p.c 74 | *_h.h 75 | *.ilk 76 | *.meta 77 | *.obj 78 | *.iobj 79 | *.pch 80 | *.pdb 81 | *.ipdb 82 | *.pgc 83 | *.pgd 84 | *.rsp 85 | *.sbr 86 | *.tlb 87 | *.tli 88 | *.tlh 89 | *.tmp 90 | *.tmp_proj 91 | *_wpftmp.csproj 92 | *.log 93 | *.vspscc 94 | *.vssscc 95 | .builds 96 | *.pidb 97 | *.svclog 98 | *.scc 99 | 100 | # Chutzpah Test files 101 | _Chutzpah* 102 | 103 | # Visual C++ cache files 104 | ipch/ 105 | *.aps 106 | *.ncb 107 | *.opendb 108 | *.opensdf 109 | *.sdf 110 | *.cachefile 111 | *.VC.db 112 | *.VC.VC.opendb 113 | 114 | # Visual Studio profiler 115 | *.psess 116 | *.vsp 117 | *.vspx 118 | *.sap 119 | 120 | # Visual Studio Trace Files 121 | *.e2e 122 | 123 | # TFS 2012 Local Workspace 124 | $tf/ 125 | 126 | # Guidance Automation Toolkit 127 | *.gpState 128 | 129 | # ReSharper is a .NET coding add-in 130 | _ReSharper*/ 131 | *.[Rr]e[Ss]harper 132 | *.DotSettings.user 133 | 134 | # TeamCity is a build add-in 135 | _TeamCity* 136 | 137 | # DotCover is a Code Coverage Tool 138 | *.dotCover 139 | 140 | # AxoCover is a Code Coverage Tool 141 | .axoCover/* 142 | !.axoCover/settings.json 143 | 144 | # Coverlet is a free, cross platform Code Coverage Tool 145 | coverage*.json 146 | coverage*.xml 147 | coverage*.info 148 | 149 | # Visual Studio code coverage results 150 | *.coverage 151 | *.coveragexml 152 | 153 | # NCrunch 154 | _NCrunch_* 155 | .*crunch*.local.xml 156 | nCrunchTemp_* 157 | 158 | # MightyMoose 159 | *.mm.* 160 | AutoTest.Net/ 161 | 162 | # Web workbench (sass) 163 | .sass-cache/ 164 | 165 | # Installshield output folder 166 | [Ee]xpress/ 167 | 168 | # DocProject is a documentation generator add-in 169 | DocProject/buildhelp/ 170 | DocProject/Help/*.HxT 171 | DocProject/Help/*.HxC 172 | DocProject/Help/*.hhc 173 | DocProject/Help/*.hhk 174 | DocProject/Help/*.hhp 175 | DocProject/Help/Html2 176 | DocProject/Help/html 177 | 178 | # Click-Once directory 179 | publish/ 180 | 181 | # Publish Web Output 182 | *.[Pp]ublish.xml 183 | *.azurePubxml 184 | # Note: Comment the next line if you want to checkin your web deploy settings, 185 | # but database connection strings (with potential passwords) will be unencrypted 186 | *.pubxml 187 | *.publishproj 188 | 189 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 190 | # checkin your Azure Web App publish settings, but sensitive information contained 191 | # in these scripts will be unencrypted 192 | PublishScripts/ 193 | 194 | # NuGet Packages 195 | *.nupkg 196 | # NuGet Symbol Packages 197 | *.snupkg 198 | # The packages folder can be ignored because of Package Restore 199 | **/[Pp]ackages/* 200 | # except build/, which is used as an MSBuild target. 201 | !**/[Pp]ackages/build/ 202 | # Uncomment if necessary however generally it will be regenerated when needed 203 | #!**/[Pp]ackages/repositories.config 204 | # NuGet v3's project.json files produces more ignorable files 205 | *.nuget.props 206 | *.nuget.targets 207 | 208 | # Microsoft Azure Build Output 209 | csx/ 210 | *.build.csdef 211 | 212 | # Microsoft Azure Emulator 213 | ecf/ 214 | rcf/ 215 | 216 | # Windows Store app package directories and files 217 | AppPackages/ 218 | BundleArtifacts/ 219 | Package.StoreAssociation.xml 220 | _pkginfo.txt 221 | *.appx 222 | *.appxbundle 223 | *.appxupload 224 | 225 | # Visual Studio cache files 226 | # files ending in .cache can be ignored 227 | *.[Cc]ache 228 | # but keep track of directories ending in .cache 229 | !?*.[Cc]ache/ 230 | 231 | # Others 232 | ClientBin/ 233 | ~$* 234 | *~ 235 | *.dbmdl 236 | *.dbproj.schemaview 237 | *.jfm 238 | *.pfx 239 | *.publishsettings 240 | orleans.codegen.cs 241 | 242 | # Including strong name files can present a security risk 243 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 244 | #*.snk 245 | 246 | # Since there are multiple workflows, uncomment next line to ignore bower_components 247 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 248 | #bower_components/ 249 | 250 | # RIA/Silverlight projects 251 | Generated_Code/ 252 | 253 | # Backup & report files from converting an old project file 254 | # to a newer Visual Studio version. Backup files are not needed, 255 | # because we have git ;-) 256 | _UpgradeReport_Files/ 257 | Backup*/ 258 | UpgradeLog*.XML 259 | UpgradeLog*.htm 260 | ServiceFabricBackup/ 261 | *.rptproj.bak 262 | 263 | # SQL Server files 264 | *.mdf 265 | *.ldf 266 | *.ndf 267 | 268 | # Business Intelligence projects 269 | *.rdl.data 270 | *.bim.layout 271 | *.bim_*.settings 272 | *.rptproj.rsuser 273 | *- [Bb]ackup.rdl 274 | *- [Bb]ackup ([0-9]).rdl 275 | *- [Bb]ackup ([0-9][0-9]).rdl 276 | 277 | # Microsoft Fakes 278 | FakesAssemblies/ 279 | 280 | # GhostDoc plugin setting file 281 | *.GhostDoc.xml 282 | 283 | # Node.js Tools for Visual Studio 284 | .ntvs_analysis.dat 285 | node_modules/ 286 | 287 | # Visual Studio 6 build log 288 | *.plg 289 | 290 | # Visual Studio 6 workspace options file 291 | *.opt 292 | 293 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 294 | *.vbw 295 | 296 | # Visual Studio LightSwitch build output 297 | **/*.HTMLClient/GeneratedArtifacts 298 | **/*.DesktopClient/GeneratedArtifacts 299 | **/*.DesktopClient/ModelManifest.xml 300 | **/*.Server/GeneratedArtifacts 301 | **/*.Server/ModelManifest.xml 302 | _Pvt_Extensions 303 | 304 | # Paket dependency manager 305 | .paket/paket.exe 306 | paket-files/ 307 | 308 | # FAKE - F# Make 309 | .fake/ 310 | 311 | # CodeRush personal settings 312 | .cr/personal 313 | 314 | # Python Tools for Visual Studio (PTVS) 315 | __pycache__/ 316 | *.pyc 317 | 318 | # Cake - Uncomment if you are using it 319 | # tools/** 320 | # !tools/packages.config 321 | 322 | # Tabs Studio 323 | *.tss 324 | 325 | # Telerik's JustMock configuration file 326 | *.jmconfig 327 | 328 | # BizTalk build output 329 | *.btp.cs 330 | *.btm.cs 331 | *.odx.cs 332 | *.xsd.cs 333 | 334 | # OpenCover UI analysis results 335 | OpenCover/ 336 | 337 | # Azure Stream Analytics local run output 338 | ASALocalRun/ 339 | 340 | # MSBuild Binary and Structured Log 341 | *.binlog 342 | 343 | # NVidia Nsight GPU debugger configuration file 344 | *.nvuser 345 | 346 | # MFractors (Xamarin productivity tool) working folder 347 | .mfractor/ 348 | 349 | # Local History for Visual Studio 350 | .localhistory/ 351 | 352 | # BeatPulse healthcheck temp database 353 | healthchecksdb 354 | 355 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 356 | MigrationBackup/ 357 | 358 | # Ionide (cross platform F# VS Code tools) working folder 359 | .ionide/ 360 | 361 | # Fody - auto-generated XML schema 362 | FodyWeavers.xsd 363 | -------------------------------------------------------------------------------- /BrowserDetectDemo/App.razor: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | Not found 8 | 9 |

Sorry, there's nothing at this address.

10 |
11 |
12 |
13 | -------------------------------------------------------------------------------- /BrowserDetectDemo/BrowserDetectDemo.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | enable 6 | enable 7 | service-worker-assets.js 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /BrowserDetectDemo/Pages/Index.razor: -------------------------------------------------------------------------------- 1 | @page "/" 2 | 3 | Demo for Browser Detect component for Blazor 4 | 5 |

This is the demo website for the Browser Detect component for 6 | Blazor WebAssembly and 7 | Blazor Server with .NET6. 8 | Firstly, if you need help or info about this component, read the 9 | post about this component 10 | and leave your message in the Forum. Now I'm detecting Windows 11! 11 |

12 | 13 | 16 | 17 |
18 | 19 | @if(Info == null) 20 | { 21 |

Loading browser info...

22 | } 23 | else 24 | { 25 | 26 | 27 | 28 | 31 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 77 | 78 | 79 | 80 | 83 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 |
29 | Property 30 | 32 | Value 33 |
Browser Name@Info.BrowserName
Browser Major@Info.BrowserMajor
Browser Version@Info.BrowserVersion
CPU Architect@(CPUInfoString ?? Info.CPUArchitect)
Device Model@Info.DeviceModel
Device Type@Info.DeviceType
Device Vendor@Info.DeviceVendor
Engine Name@Info.EngineName
Engine Version@Info.EngineVersion
75 | GPU Renderer 76 | @Info.GPURenderer
81 | GPU Vendor 82 | 84 | @Info.GPUVendor 85 |
Is Desktop?@Info.IsDesktop
Is Mobile?@Info.IsMobile
Is Tablet?@Info.IsTablet
Is Android?@Info.IsAndroid
Is iPhone?@Info.IsIPhone
Is iPad?@Info.IsIPad
Is iPad Pro?@Info.IsIPadPro
Operating System Name@Info.OSName
Operating System Version 122 | @(OSInfo ?? Info.OSVersion) 123 |
Screen Resolution@Info.ScreenResolution
Time Zone@Info.TimeZone
User Agent@Info.UserAgent
139 | } 140 | 141 |

142 | 143 | @code { 144 | public BrowserInfo? Info { get; set; } 145 | public string? OSInfo { get; set; } = ""; 146 | public string? CPUInfoString { get; set; } 147 | 148 | private void OSArchitectureString(string cpu) 149 | { 150 | CPUInfoString = cpu; 151 | } 152 | 153 | private void OSUpdateString(string version) 154 | { 155 | OSInfo = version; 156 | } 157 | } -------------------------------------------------------------------------------- /BrowserDetectDemo/Program.cs: -------------------------------------------------------------------------------- 1 | using BrowserDetectDemo; 2 | using Microsoft.AspNetCore.Components.Web; 3 | using Microsoft.AspNetCore.Components.WebAssembly.Hosting; 4 | 5 | var builder = WebAssemblyHostBuilder.CreateDefault(args); 6 | builder.RootComponents.Add("#app"); 7 | builder.RootComponents.Add("head::after"); 8 | 9 | builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); 10 | 11 | await builder.Build().RunAsync(); 12 | -------------------------------------------------------------------------------- /BrowserDetectDemo/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:11345", 7 | "sslPort": 44315 8 | } 9 | }, 10 | "profiles": { 11 | "BrowserDetectDemo": { 12 | "commandName": "Project", 13 | "launchBrowser": true, 14 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", 15 | "applicationUrl": "https://localhost:7047;http://localhost:5047", 16 | "environmentVariables": { 17 | "ASPNETCORE_ENVIRONMENT": "Development" 18 | }, 19 | "dotnetRunMessages": true 20 | }, 21 | "IIS Express": { 22 | "commandName": "IISExpress", 23 | "launchBrowser": true, 24 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", 25 | "environmentVariables": { 26 | "ASPNETCORE_ENVIRONMENT": "Development" 27 | } 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /BrowserDetectDemo/Shared/MainLayout.razor: -------------------------------------------------------------------------------- 1 | @inherits LayoutComponentBase 2 | 3 |
4 | 7 | 8 |
9 | 15 | 16 |
17 | @Body 18 |
19 |
20 |
21 | -------------------------------------------------------------------------------- /BrowserDetectDemo/Shared/MainLayout.razor.css: -------------------------------------------------------------------------------- 1 | .page { 2 | position: relative; 3 | display: flex; 4 | flex-direction: column; 5 | } 6 | 7 | main { 8 | flex: 1; 9 | } 10 | 11 | .sidebar { 12 | background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%); 13 | } 14 | 15 | .top-row { 16 | background-color: #f7f7f7; 17 | border-bottom: 1px solid #d6d5d5; 18 | justify-content: flex-end; 19 | height: 3.5rem; 20 | display: flex; 21 | align-items: center; 22 | } 23 | 24 | .top-row ::deep a, .top-row ::deep .btn-link { 25 | white-space: nowrap; 26 | margin-left: 1.5rem; 27 | text-decoration: none; 28 | } 29 | 30 | .top-row ::deep a:hover, .top-row ::deep .btn-link:hover { 31 | text-decoration: underline; 32 | } 33 | 34 | .top-row ::deep a:first-child { 35 | overflow: hidden; 36 | text-overflow: ellipsis; 37 | } 38 | 39 | @media (max-width: 640.98px) { 40 | .top-row:not(.auth) { 41 | display: none; 42 | } 43 | 44 | .top-row.auth { 45 | justify-content: space-between; 46 | } 47 | 48 | .top-row ::deep a, .top-row ::deep .btn-link { 49 | margin-left: 0; 50 | } 51 | } 52 | 53 | @media (min-width: 641px) { 54 | .page { 55 | flex-direction: row; 56 | } 57 | 58 | .sidebar { 59 | width: 250px; 60 | height: 100vh; 61 | position: sticky; 62 | top: 0; 63 | } 64 | 65 | .top-row { 66 | position: sticky; 67 | top: 0; 68 | z-index: 1; 69 | } 70 | 71 | .top-row.auth ::deep a:first-child { 72 | flex: 1; 73 | text-align: right; 74 | width: 0; 75 | } 76 | 77 | .top-row, article { 78 | padding-left: 2rem !important; 79 | padding-right: 1.5rem !important; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /BrowserDetectDemo/Shared/NavMenu.razor: -------------------------------------------------------------------------------- 1 |  9 | 10 |
11 | 18 |
19 | 20 | @code { 21 | private bool collapseNavMenu = true; 22 | 23 | private string? NavMenuCssClass => collapseNavMenu ? "collapse" : null; 24 | 25 | private void ToggleNavMenu() 26 | { 27 | collapseNavMenu = !collapseNavMenu; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /BrowserDetectDemo/Shared/NavMenu.razor.css: -------------------------------------------------------------------------------- 1 | .navbar-toggler { 2 | background-color: rgba(255, 255, 255, 0.1); 3 | } 4 | 5 | .top-row { 6 | height: 3.5rem; 7 | background-color: rgba(0,0,0,0.4); 8 | } 9 | 10 | .navbar-brand { 11 | font-size: 1.1rem; 12 | } 13 | 14 | .oi { 15 | width: 2rem; 16 | font-size: 1.1rem; 17 | vertical-align: text-top; 18 | top: -2px; 19 | } 20 | 21 | .nav-item { 22 | font-size: 0.9rem; 23 | padding-bottom: 0.5rem; 24 | } 25 | 26 | .nav-item:first-of-type { 27 | padding-top: 1rem; 28 | } 29 | 30 | .nav-item:last-of-type { 31 | padding-bottom: 1rem; 32 | } 33 | 34 | .nav-item ::deep a { 35 | color: #d7d7d7; 36 | border-radius: 4px; 37 | height: 3rem; 38 | display: flex; 39 | align-items: center; 40 | line-height: 3rem; 41 | } 42 | 43 | .nav-item ::deep a.active { 44 | background-color: rgba(255,255,255,0.25); 45 | color: white; 46 | } 47 | 48 | .nav-item ::deep a:hover { 49 | background-color: rgba(255,255,255,0.1); 50 | color: white; 51 | } 52 | 53 | @media (min-width: 641px) { 54 | .navbar-toggler { 55 | display: none; 56 | } 57 | 58 | .collapse { 59 | /* Never collapse the sidebar for wide screens */ 60 | display: block; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /BrowserDetectDemo/_Imports.razor: -------------------------------------------------------------------------------- 1 | @using System.Net.Http 2 | @using System.Net.Http.Json 3 | @using Microsoft.AspNetCore.Components.Forms 4 | @using Microsoft.AspNetCore.Components.Routing 5 | @using Microsoft.AspNetCore.Components.Web 6 | @using Microsoft.AspNetCore.Components.Web.Virtualization 7 | @using Microsoft.AspNetCore.Components.WebAssembly.Http 8 | @using Microsoft.JSInterop 9 | @using BrowserDetectDemo 10 | @using BrowserDetectDemo.Shared 11 | 12 | @using PSC.Blazor.Components.BrowserDetect -------------------------------------------------------------------------------- /BrowserDetectDemo/wwwroot/css/app.css: -------------------------------------------------------------------------------- 1 | @import url('open-iconic/font/css/open-iconic-bootstrap.min.css'); 2 | 3 | html, body { 4 | font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; 5 | } 6 | 7 | h1:focus { 8 | outline: none; 9 | } 10 | 11 | a, .btn-link { 12 | color: #0071c1; 13 | } 14 | 15 | .btn-primary { 16 | color: #fff; 17 | background-color: #1b6ec2; 18 | border-color: #1861ac; 19 | } 20 | 21 | .content { 22 | padding-top: 1.1rem; 23 | } 24 | 25 | .valid.modified:not([type=checkbox]) { 26 | outline: 1px solid #26b050; 27 | } 28 | 29 | .invalid { 30 | outline: 1px solid red; 31 | } 32 | 33 | .validation-message { 34 | color: red; 35 | } 36 | 37 | #blazor-error-ui { 38 | background: lightyellow; 39 | bottom: 0; 40 | box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2); 41 | display: none; 42 | left: 0; 43 | padding: 0.6rem 1.25rem 0.7rem 1.25rem; 44 | position: fixed; 45 | width: 100%; 46 | z-index: 1000; 47 | } 48 | 49 | #blazor-error-ui .dismiss { 50 | cursor: pointer; 51 | position: absolute; 52 | right: 0.75rem; 53 | top: 0.5rem; 54 | } 55 | 56 | .blazor-error-boundary { 57 | background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121; 58 | padding: 1rem 1rem 1rem 3.7rem; 59 | color: white; 60 | } 61 | 62 | .blazor-error-boundary::after { 63 | content: "An error has occurred." 64 | } 65 | 66 | table { 67 | width: 100%; 68 | border-collapse: collapse; 69 | } 70 | /* Zebra striping */ 71 | tr:nth-of-type(odd) { 72 | background: #eee; 73 | } 74 | 75 | th { 76 | background: #333; 77 | color: white; 78 | font-weight: bold; 79 | } 80 | 81 | td, th { 82 | padding: 6px; 83 | border: 1px solid #ccc; 84 | text-align: left; 85 | } -------------------------------------------------------------------------------- /BrowserDetectDemo/wwwroot/css/open-iconic/FONT-LICENSE: -------------------------------------------------------------------------------- 1 | SIL OPEN FONT LICENSE Version 1.1 2 | 3 | Copyright (c) 2014 Waybury 4 | 5 | PREAMBLE 6 | The goals of the Open Font License (OFL) are to stimulate worldwide 7 | development of collaborative font projects, to support the font creation 8 | efforts of academic and linguistic communities, and to provide a free and 9 | open framework in which fonts may be shared and improved in partnership 10 | with others. 11 | 12 | The OFL allows the licensed fonts to be used, studied, modified and 13 | redistributed freely as long as they are not sold by themselves. The 14 | fonts, including any derivative works, can be bundled, embedded, 15 | redistributed and/or sold with any software provided that any reserved 16 | names are not used by derivative works. The fonts and derivatives, 17 | however, cannot be released under any other type of license. The 18 | requirement for fonts to remain under this license does not apply 19 | to any document created using the fonts or their derivatives. 20 | 21 | DEFINITIONS 22 | "Font Software" refers to the set of files released by the Copyright 23 | Holder(s) under this license and clearly marked as such. This may 24 | include source files, build scripts and documentation. 25 | 26 | "Reserved Font Name" refers to any names specified as such after the 27 | copyright statement(s). 28 | 29 | "Original Version" refers to the collection of Font Software components as 30 | distributed by the Copyright Holder(s). 31 | 32 | "Modified Version" refers to any derivative made by adding to, deleting, 33 | or substituting -- in part or in whole -- any of the components of the 34 | Original Version, by changing formats or by porting the Font Software to a 35 | new environment. 36 | 37 | "Author" refers to any designer, engineer, programmer, technical 38 | writer or other person who contributed to the Font Software. 39 | 40 | PERMISSION & CONDITIONS 41 | Permission is hereby granted, free of charge, to any person obtaining 42 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 43 | redistribute, and sell modified and unmodified copies of the Font 44 | Software, subject to the following conditions: 45 | 46 | 1) Neither the Font Software nor any of its individual components, 47 | in Original or Modified Versions, may be sold by itself. 48 | 49 | 2) Original or Modified Versions of the Font Software may be bundled, 50 | redistributed and/or sold with any software, provided that each copy 51 | contains the above copyright notice and this license. These can be 52 | included either as stand-alone text files, human-readable headers or 53 | in the appropriate machine-readable metadata fields within text or 54 | binary files as long as those fields can be easily viewed by the user. 55 | 56 | 3) No Modified Version of the Font Software may use the Reserved Font 57 | Name(s) unless explicit written permission is granted by the corresponding 58 | Copyright Holder. This restriction only applies to the primary font name as 59 | presented to the users. 60 | 61 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 62 | Software shall not be used to promote, endorse or advertise any 63 | Modified Version, except to acknowledge the contribution(s) of the 64 | Copyright Holder(s) and the Author(s) or with their explicit written 65 | permission. 66 | 67 | 5) The Font Software, modified or unmodified, in part or in whole, 68 | must be distributed entirely under this license, and must not be 69 | distributed under any other license. The requirement for fonts to 70 | remain under this license does not apply to any document created 71 | using the Font Software. 72 | 73 | TERMINATION 74 | This license becomes null and void if any of the above conditions are 75 | not met. 76 | 77 | DISCLAIMER 78 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 79 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 80 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 81 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 82 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 83 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 84 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 85 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 86 | OTHER DEALINGS IN THE FONT SOFTWARE. 87 | -------------------------------------------------------------------------------- /BrowserDetectDemo/wwwroot/css/open-iconic/ICON-LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Waybury 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /BrowserDetectDemo/wwwroot/css/open-iconic/README.md: -------------------------------------------------------------------------------- 1 | [Open Iconic v1.1.1](http://useiconic.com/open) 2 | =========== 3 | 4 | ### Open Iconic is the open source sibling of [Iconic](http://useiconic.com). It is a hyper-legible collection of 223 icons with a tiny footprint—ready to use with Bootstrap and Foundation. [View the collection](http://useiconic.com/open#icons) 5 | 6 | 7 | 8 | ## What's in Open Iconic? 9 | 10 | * 223 icons designed to be legible down to 8 pixels 11 | * Super-light SVG files - 61.8 for the entire set 12 | * SVG sprite—the modern replacement for icon fonts 13 | * Webfont (EOT, OTF, SVG, TTF, WOFF), PNG and WebP formats 14 | * Webfont stylesheets (including versions for Bootstrap and Foundation) in CSS, LESS, SCSS and Stylus formats 15 | * PNG and WebP raster images in 8px, 16px, 24px, 32px, 48px and 64px. 16 | 17 | 18 | ## Getting Started 19 | 20 | #### For code samples and everything else you need to get started with Open Iconic, check out our [Icons](http://useiconic.com/open#icons) and [Reference](http://useiconic.com/open#reference) sections. 21 | 22 | ### General Usage 23 | 24 | #### Using Open Iconic's SVGs 25 | 26 | We like SVGs and we think they're the way to display icons on the web. Since Open Iconic are just basic SVGs, we suggest you display them like you would any other image (don't forget the `alt` attribute). 27 | 28 | ``` 29 | icon name 30 | ``` 31 | 32 | #### Using Open Iconic's SVG Sprite 33 | 34 | Open Iconic also comes in a SVG sprite which allows you to display all the icons in the set with a single request. It's like an icon font, without being a hack. 35 | 36 | Adding an icon from an SVG sprite is a little different than what you're used to, but it's still a piece of cake. *Tip: To make your icons easily style able, we suggest adding a general class to the* `` *tag and a unique class name for each different icon in the* `` *tag.* 37 | 38 | ``` 39 | 40 | 41 | 42 | ``` 43 | 44 | Sizing icons only needs basic CSS. All the icons are in a square format, so just set the `` tag with equal width and height dimensions. 45 | 46 | ``` 47 | .icon { 48 | width: 16px; 49 | height: 16px; 50 | } 51 | ``` 52 | 53 | Coloring icons is even easier. All you need to do is set the `fill` rule on the `` tag. 54 | 55 | ``` 56 | .icon-account-login { 57 | fill: #f00; 58 | } 59 | ``` 60 | 61 | To learn more about SVG Sprites, read [Chris Coyier's guide](http://css-tricks.com/svg-sprites-use-better-icon-fonts/). 62 | 63 | #### Using Open Iconic's Icon Font... 64 | 65 | 66 | ##### …with Bootstrap 67 | 68 | You can find our Bootstrap stylesheets in `font/css/open-iconic-bootstrap.{css, less, scss, styl}` 69 | 70 | 71 | ``` 72 | 73 | ``` 74 | 75 | 76 | ``` 77 | 78 | ``` 79 | 80 | ##### …with Foundation 81 | 82 | You can find our Foundation stylesheets in `font/css/open-iconic-foundation.{css, less, scss, styl}` 83 | 84 | ``` 85 | 86 | ``` 87 | 88 | 89 | ``` 90 | 91 | ``` 92 | 93 | ##### …on its own 94 | 95 | You can find our default stylesheets in `font/css/open-iconic.{css, less, scss, styl}` 96 | 97 | ``` 98 | 99 | ``` 100 | 101 | ``` 102 | 103 | ``` 104 | 105 | 106 | ## License 107 | 108 | ### Icons 109 | 110 | All code (including SVG markup) is under the [MIT License](http://opensource.org/licenses/MIT). 111 | 112 | ### Fonts 113 | 114 | All fonts are under the [SIL Licensed](http://scripts.sil.org/cms/scripts/page.php?item_id=OFL_web). 115 | -------------------------------------------------------------------------------- /BrowserDetectDemo/wwwroot/css/open-iconic/font/css/open-iconic-bootstrap.min.css: -------------------------------------------------------------------------------- 1 | @font-face{font-family:Icons;src:url(../fonts/open-iconic.eot);src:url(../fonts/open-iconic.eot?#iconic-sm) format('embedded-opentype'),url(../fonts/open-iconic.woff) format('woff'),url(../fonts/open-iconic.ttf) format('truetype'),url(../fonts/open-iconic.otf) format('opentype'),url(../fonts/open-iconic.svg#iconic-sm) format('svg');font-weight:400;font-style:normal}.oi{position:relative;top:1px;display:inline-block;speak:none;font-family:Icons;font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.oi:empty:before{width:1em;text-align:center;box-sizing:content-box}.oi.oi-align-center:before{text-align:center}.oi.oi-align-left:before{text-align:left}.oi.oi-align-right:before{text-align:right}.oi.oi-flip-horizontal:before{-webkit-transform:scale(-1,1);-ms-transform:scale(-1,1);transform:scale(-1,1)}.oi.oi-flip-vertical:before{-webkit-transform:scale(1,-1);-ms-transform:scale(-1,1);transform:scale(1,-1)}.oi.oi-flip-horizontal-vertical:before{-webkit-transform:scale(-1,-1);-ms-transform:scale(-1,1);transform:scale(-1,-1)}.oi-account-login:before{content:'\e000'}.oi-account-logout:before{content:'\e001'}.oi-action-redo:before{content:'\e002'}.oi-action-undo:before{content:'\e003'}.oi-align-center:before{content:'\e004'}.oi-align-left:before{content:'\e005'}.oi-align-right:before{content:'\e006'}.oi-aperture:before{content:'\e007'}.oi-arrow-bottom:before{content:'\e008'}.oi-arrow-circle-bottom:before{content:'\e009'}.oi-arrow-circle-left:before{content:'\e00a'}.oi-arrow-circle-right:before{content:'\e00b'}.oi-arrow-circle-top:before{content:'\e00c'}.oi-arrow-left:before{content:'\e00d'}.oi-arrow-right:before{content:'\e00e'}.oi-arrow-thick-bottom:before{content:'\e00f'}.oi-arrow-thick-left:before{content:'\e010'}.oi-arrow-thick-right:before{content:'\e011'}.oi-arrow-thick-top:before{content:'\e012'}.oi-arrow-top:before{content:'\e013'}.oi-audio-spectrum:before{content:'\e014'}.oi-audio:before{content:'\e015'}.oi-badge:before{content:'\e016'}.oi-ban:before{content:'\e017'}.oi-bar-chart:before{content:'\e018'}.oi-basket:before{content:'\e019'}.oi-battery-empty:before{content:'\e01a'}.oi-battery-full:before{content:'\e01b'}.oi-beaker:before{content:'\e01c'}.oi-bell:before{content:'\e01d'}.oi-bluetooth:before{content:'\e01e'}.oi-bold:before{content:'\e01f'}.oi-bolt:before{content:'\e020'}.oi-book:before{content:'\e021'}.oi-bookmark:before{content:'\e022'}.oi-box:before{content:'\e023'}.oi-briefcase:before{content:'\e024'}.oi-british-pound:before{content:'\e025'}.oi-browser:before{content:'\e026'}.oi-brush:before{content:'\e027'}.oi-bug:before{content:'\e028'}.oi-bullhorn:before{content:'\e029'}.oi-calculator:before{content:'\e02a'}.oi-calendar:before{content:'\e02b'}.oi-camera-slr:before{content:'\e02c'}.oi-caret-bottom:before{content:'\e02d'}.oi-caret-left:before{content:'\e02e'}.oi-caret-right:before{content:'\e02f'}.oi-caret-top:before{content:'\e030'}.oi-cart:before{content:'\e031'}.oi-chat:before{content:'\e032'}.oi-check:before{content:'\e033'}.oi-chevron-bottom:before{content:'\e034'}.oi-chevron-left:before{content:'\e035'}.oi-chevron-right:before{content:'\e036'}.oi-chevron-top:before{content:'\e037'}.oi-circle-check:before{content:'\e038'}.oi-circle-x:before{content:'\e039'}.oi-clipboard:before{content:'\e03a'}.oi-clock:before{content:'\e03b'}.oi-cloud-download:before{content:'\e03c'}.oi-cloud-upload:before{content:'\e03d'}.oi-cloud:before{content:'\e03e'}.oi-cloudy:before{content:'\e03f'}.oi-code:before{content:'\e040'}.oi-cog:before{content:'\e041'}.oi-collapse-down:before{content:'\e042'}.oi-collapse-left:before{content:'\e043'}.oi-collapse-right:before{content:'\e044'}.oi-collapse-up:before{content:'\e045'}.oi-command:before{content:'\e046'}.oi-comment-square:before{content:'\e047'}.oi-compass:before{content:'\e048'}.oi-contrast:before{content:'\e049'}.oi-copywriting:before{content:'\e04a'}.oi-credit-card:before{content:'\e04b'}.oi-crop:before{content:'\e04c'}.oi-dashboard:before{content:'\e04d'}.oi-data-transfer-download:before{content:'\e04e'}.oi-data-transfer-upload:before{content:'\e04f'}.oi-delete:before{content:'\e050'}.oi-dial:before{content:'\e051'}.oi-document:before{content:'\e052'}.oi-dollar:before{content:'\e053'}.oi-double-quote-sans-left:before{content:'\e054'}.oi-double-quote-sans-right:before{content:'\e055'}.oi-double-quote-serif-left:before{content:'\e056'}.oi-double-quote-serif-right:before{content:'\e057'}.oi-droplet:before{content:'\e058'}.oi-eject:before{content:'\e059'}.oi-elevator:before{content:'\e05a'}.oi-ellipses:before{content:'\e05b'}.oi-envelope-closed:before{content:'\e05c'}.oi-envelope-open:before{content:'\e05d'}.oi-euro:before{content:'\e05e'}.oi-excerpt:before{content:'\e05f'}.oi-expand-down:before{content:'\e060'}.oi-expand-left:before{content:'\e061'}.oi-expand-right:before{content:'\e062'}.oi-expand-up:before{content:'\e063'}.oi-external-link:before{content:'\e064'}.oi-eye:before{content:'\e065'}.oi-eyedropper:before{content:'\e066'}.oi-file:before{content:'\e067'}.oi-fire:before{content:'\e068'}.oi-flag:before{content:'\e069'}.oi-flash:before{content:'\e06a'}.oi-folder:before{content:'\e06b'}.oi-fork:before{content:'\e06c'}.oi-fullscreen-enter:before{content:'\e06d'}.oi-fullscreen-exit:before{content:'\e06e'}.oi-globe:before{content:'\e06f'}.oi-graph:before{content:'\e070'}.oi-grid-four-up:before{content:'\e071'}.oi-grid-three-up:before{content:'\e072'}.oi-grid-two-up:before{content:'\e073'}.oi-hard-drive:before{content:'\e074'}.oi-header:before{content:'\e075'}.oi-headphones:before{content:'\e076'}.oi-heart:before{content:'\e077'}.oi-home:before{content:'\e078'}.oi-image:before{content:'\e079'}.oi-inbox:before{content:'\e07a'}.oi-infinity:before{content:'\e07b'}.oi-info:before{content:'\e07c'}.oi-italic:before{content:'\e07d'}.oi-justify-center:before{content:'\e07e'}.oi-justify-left:before{content:'\e07f'}.oi-justify-right:before{content:'\e080'}.oi-key:before{content:'\e081'}.oi-laptop:before{content:'\e082'}.oi-layers:before{content:'\e083'}.oi-lightbulb:before{content:'\e084'}.oi-link-broken:before{content:'\e085'}.oi-link-intact:before{content:'\e086'}.oi-list-rich:before{content:'\e087'}.oi-list:before{content:'\e088'}.oi-location:before{content:'\e089'}.oi-lock-locked:before{content:'\e08a'}.oi-lock-unlocked:before{content:'\e08b'}.oi-loop-circular:before{content:'\e08c'}.oi-loop-square:before{content:'\e08d'}.oi-loop:before{content:'\e08e'}.oi-magnifying-glass:before{content:'\e08f'}.oi-map-marker:before{content:'\e090'}.oi-map:before{content:'\e091'}.oi-media-pause:before{content:'\e092'}.oi-media-play:before{content:'\e093'}.oi-media-record:before{content:'\e094'}.oi-media-skip-backward:before{content:'\e095'}.oi-media-skip-forward:before{content:'\e096'}.oi-media-step-backward:before{content:'\e097'}.oi-media-step-forward:before{content:'\e098'}.oi-media-stop:before{content:'\e099'}.oi-medical-cross:before{content:'\e09a'}.oi-menu:before{content:'\e09b'}.oi-microphone:before{content:'\e09c'}.oi-minus:before{content:'\e09d'}.oi-monitor:before{content:'\e09e'}.oi-moon:before{content:'\e09f'}.oi-move:before{content:'\e0a0'}.oi-musical-note:before{content:'\e0a1'}.oi-paperclip:before{content:'\e0a2'}.oi-pencil:before{content:'\e0a3'}.oi-people:before{content:'\e0a4'}.oi-person:before{content:'\e0a5'}.oi-phone:before{content:'\e0a6'}.oi-pie-chart:before{content:'\e0a7'}.oi-pin:before{content:'\e0a8'}.oi-play-circle:before{content:'\e0a9'}.oi-plus:before{content:'\e0aa'}.oi-power-standby:before{content:'\e0ab'}.oi-print:before{content:'\e0ac'}.oi-project:before{content:'\e0ad'}.oi-pulse:before{content:'\e0ae'}.oi-puzzle-piece:before{content:'\e0af'}.oi-question-mark:before{content:'\e0b0'}.oi-rain:before{content:'\e0b1'}.oi-random:before{content:'\e0b2'}.oi-reload:before{content:'\e0b3'}.oi-resize-both:before{content:'\e0b4'}.oi-resize-height:before{content:'\e0b5'}.oi-resize-width:before{content:'\e0b6'}.oi-rss-alt:before{content:'\e0b7'}.oi-rss:before{content:'\e0b8'}.oi-script:before{content:'\e0b9'}.oi-share-boxed:before{content:'\e0ba'}.oi-share:before{content:'\e0bb'}.oi-shield:before{content:'\e0bc'}.oi-signal:before{content:'\e0bd'}.oi-signpost:before{content:'\e0be'}.oi-sort-ascending:before{content:'\e0bf'}.oi-sort-descending:before{content:'\e0c0'}.oi-spreadsheet:before{content:'\e0c1'}.oi-star:before{content:'\e0c2'}.oi-sun:before{content:'\e0c3'}.oi-tablet:before{content:'\e0c4'}.oi-tag:before{content:'\e0c5'}.oi-tags:before{content:'\e0c6'}.oi-target:before{content:'\e0c7'}.oi-task:before{content:'\e0c8'}.oi-terminal:before{content:'\e0c9'}.oi-text:before{content:'\e0ca'}.oi-thumb-down:before{content:'\e0cb'}.oi-thumb-up:before{content:'\e0cc'}.oi-timer:before{content:'\e0cd'}.oi-transfer:before{content:'\e0ce'}.oi-trash:before{content:'\e0cf'}.oi-underline:before{content:'\e0d0'}.oi-vertical-align-bottom:before{content:'\e0d1'}.oi-vertical-align-center:before{content:'\e0d2'}.oi-vertical-align-top:before{content:'\e0d3'}.oi-video:before{content:'\e0d4'}.oi-volume-high:before{content:'\e0d5'}.oi-volume-low:before{content:'\e0d6'}.oi-volume-off:before{content:'\e0d7'}.oi-warning:before{content:'\e0d8'}.oi-wifi:before{content:'\e0d9'}.oi-wrench:before{content:'\e0da'}.oi-x:before{content:'\e0db'}.oi-yen:before{content:'\e0dc'}.oi-zoom-in:before{content:'\e0dd'}.oi-zoom-out:before{content:'\e0de'} -------------------------------------------------------------------------------- /BrowserDetectDemo/wwwroot/css/open-iconic/font/fonts/open-iconic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erossini/BlazorBrowserDetect/517dd91f890a6503061ccdfff252382a0b63c405/BrowserDetectDemo/wwwroot/css/open-iconic/font/fonts/open-iconic.eot -------------------------------------------------------------------------------- /BrowserDetectDemo/wwwroot/css/open-iconic/font/fonts/open-iconic.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erossini/BlazorBrowserDetect/517dd91f890a6503061ccdfff252382a0b63c405/BrowserDetectDemo/wwwroot/css/open-iconic/font/fonts/open-iconic.otf -------------------------------------------------------------------------------- /BrowserDetectDemo/wwwroot/css/open-iconic/font/fonts/open-iconic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | Created by FontForge 20120731 at Tue Jul 1 20:39:22 2014 9 | By P.J. Onori 10 | Created by P.J. Onori with FontForge 2.0 (http://fontforge.sf.net) 11 | 12 | 13 | 14 | 27 | 28 | 30 | 32 | 34 | 36 | 38 | 40 | 42 | 45 | 47 | 49 | 51 | 53 | 55 | 57 | 59 | 61 | 63 | 65 | 67 | 69 | 71 | 74 | 76 | 79 | 81 | 84 | 86 | 88 | 91 | 93 | 95 | 98 | 100 | 102 | 104 | 106 | 109 | 112 | 115 | 117 | 121 | 123 | 125 | 127 | 130 | 132 | 134 | 136 | 138 | 141 | 143 | 145 | 147 | 149 | 151 | 153 | 155 | 157 | 159 | 162 | 165 | 167 | 169 | 172 | 174 | 177 | 179 | 181 | 183 | 185 | 189 | 191 | 194 | 196 | 198 | 200 | 202 | 205 | 207 | 209 | 211 | 213 | 215 | 218 | 220 | 222 | 224 | 226 | 228 | 230 | 232 | 234 | 236 | 238 | 241 | 243 | 245 | 247 | 249 | 251 | 253 | 256 | 259 | 261 | 263 | 265 | 267 | 269 | 272 | 274 | 276 | 280 | 282 | 285 | 287 | 289 | 292 | 295 | 298 | 300 | 302 | 304 | 306 | 309 | 312 | 314 | 316 | 318 | 320 | 322 | 324 | 326 | 330 | 334 | 338 | 340 | 343 | 345 | 347 | 349 | 351 | 353 | 355 | 358 | 360 | 363 | 365 | 367 | 369 | 371 | 373 | 375 | 377 | 379 | 381 | 383 | 386 | 388 | 390 | 392 | 394 | 396 | 399 | 401 | 404 | 406 | 408 | 410 | 412 | 414 | 416 | 419 | 421 | 423 | 425 | 428 | 431 | 435 | 438 | 440 | 442 | 444 | 446 | 448 | 451 | 453 | 455 | 457 | 460 | 462 | 464 | 466 | 468 | 471 | 473 | 477 | 479 | 481 | 483 | 486 | 488 | 490 | 492 | 494 | 496 | 499 | 501 | 504 | 506 | 509 | 512 | 515 | 517 | 520 | 522 | 524 | 526 | 529 | 532 | 534 | 536 | 539 | 542 | 543 | 544 | -------------------------------------------------------------------------------- /BrowserDetectDemo/wwwroot/css/open-iconic/font/fonts/open-iconic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erossini/BlazorBrowserDetect/517dd91f890a6503061ccdfff252382a0b63c405/BrowserDetectDemo/wwwroot/css/open-iconic/font/fonts/open-iconic.ttf -------------------------------------------------------------------------------- /BrowserDetectDemo/wwwroot/css/open-iconic/font/fonts/open-iconic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erossini/BlazorBrowserDetect/517dd91f890a6503061ccdfff252382a0b63c405/BrowserDetectDemo/wwwroot/css/open-iconic/font/fonts/open-iconic.woff -------------------------------------------------------------------------------- /BrowserDetectDemo/wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erossini/BlazorBrowserDetect/517dd91f890a6503061ccdfff252382a0b63c405/BrowserDetectDemo/wwwroot/favicon.ico -------------------------------------------------------------------------------- /BrowserDetectDemo/wwwroot/icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erossini/BlazorBrowserDetect/517dd91f890a6503061ccdfff252382a0b63c405/BrowserDetectDemo/wwwroot/icon-192.png -------------------------------------------------------------------------------- /BrowserDetectDemo/wwwroot/icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erossini/BlazorBrowserDetect/517dd91f890a6503061ccdfff252382a0b63c405/BrowserDetectDemo/wwwroot/icon-512.png -------------------------------------------------------------------------------- /BrowserDetectDemo/wwwroot/images/psc_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erossini/BlazorBrowserDetect/517dd91f890a6503061ccdfff252382a0b63c405/BrowserDetectDemo/wwwroot/images/psc_logo.png -------------------------------------------------------------------------------- /BrowserDetectDemo/wwwroot/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Browser Detect Demo for Blazor 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 128 | 129 |
PureSourceCode.com
130 |
131 | is detecting your browser 132 |
133 |
134 | 135 |
136 | An unhandled error has occurred. 137 | Reload 138 | 🗙 139 |
140 | 141 | 142 | 143 | 144 | 145 | -------------------------------------------------------------------------------- /BrowserDetectDemo/wwwroot/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "UrlShortener.WebApp", 3 | "short_name": "UrlShortener.WebApp", 4 | "start_url": "./", 5 | "display": "standalone", 6 | "background_color": "#ffffff", 7 | "theme_color": "#03173d", 8 | "prefer_related_applications": false, 9 | "icons": [ 10 | { 11 | "src": "icon-512.png", 12 | "type": "image/png", 13 | "sizes": "512x512" 14 | }, 15 | { 16 | "src": "icon-192.png", 17 | "type": "image/png", 18 | "sizes": "192x192" 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /BrowserDetectDemo/wwwroot/service-worker.js: -------------------------------------------------------------------------------- 1 | // In development, always fetch from the network and do not enable offline support. 2 | // This is because caching would make development more difficult (changes would not 3 | // be reflected on the first load after each change). 4 | self.addEventListener('fetch', () => { }); 5 | -------------------------------------------------------------------------------- /BrowserDetectDemo/wwwroot/service-worker.published.js: -------------------------------------------------------------------------------- 1 | // Caution! Be sure you understand the caveats before publishing an application with 2 | // offline support. See https://aka.ms/blazor-offline-considerations 3 | 4 | self.importScripts('./service-worker-assets.js'); 5 | self.addEventListener('install', event => event.waitUntil(onInstall(event))); 6 | self.addEventListener('activate', event => event.waitUntil(onActivate(event))); 7 | self.addEventListener('fetch', event => event.respondWith(onFetch(event))); 8 | 9 | const cacheNamePrefix = 'offline-cache-'; 10 | const cacheName = `${cacheNamePrefix}${self.assetsManifest.version}`; 11 | const offlineAssetsInclude = [ /\.dll$/, /\.pdb$/, /\.wasm/, /\.html/, /\.js$/, /\.json$/, /\.css$/, /\.woff$/, /\.png$/, /\.jpe?g$/, /\.gif$/, /\.ico$/, /\.blat$/, /\.dat$/ ]; 12 | const offlineAssetsExclude = [ /^service-worker\.js$/ ]; 13 | 14 | async function onInstall(event) { 15 | console.info('Service worker: Install'); 16 | 17 | // Fetch and cache all matching items from the assets manifest 18 | const assetsRequests = self.assetsManifest.assets 19 | .filter(asset => offlineAssetsInclude.some(pattern => pattern.test(asset.url))) 20 | .filter(asset => !offlineAssetsExclude.some(pattern => pattern.test(asset.url))) 21 | .map(asset => new Request(asset.url, { integrity: asset.hash, cache: 'no-cache' })); 22 | await caches.open(cacheName).then(cache => cache.addAll(assetsRequests)); 23 | } 24 | 25 | async function onActivate(event) { 26 | console.info('Service worker: Activate'); 27 | 28 | // Delete unused caches 29 | const cacheKeys = await caches.keys(); 30 | await Promise.all(cacheKeys 31 | .filter(key => key.startsWith(cacheNamePrefix) && key !== cacheName) 32 | .map(key => caches.delete(key))); 33 | } 34 | 35 | async function onFetch(event) { 36 | let cachedResponse = null; 37 | if (event.request.method === 'GET') { 38 | // For all navigation requests, try to serve index.html from cache 39 | // If you need some URLs to be server-rendered, edit the following check to exclude those URLs 40 | const shouldServeIndexHtml = event.request.mode === 'navigate'; 41 | 42 | const request = shouldServeIndexHtml ? 'index.html' : event.request; 43 | const cache = await caches.open(cacheName); 44 | cachedResponse = await cache.match(request); 45 | } 46 | 47 | return cachedResponse || fetch(event.request); 48 | } 49 | -------------------------------------------------------------------------------- /PSC.Blazor.Components.BrowserDetect.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.0.32112.339 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PSC.Blazor.Components.BrowserDetect", "src\PSC.Blazor.Components.BrowserDetect.csproj", "{E6A105EE-D6F5-476E-B261-B93C9BF480CA}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BrowserDetectDemo", "BrowserDetectDemo\BrowserDetectDemo.csproj", "{07176B1F-8C18-445F-9D57-0B4609C3C685}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Other files", "Other files", "{83B379A2-C3D5-4CB2-AA91-7051AD526D3F}" 11 | ProjectSection(SolutionItems) = preProject 12 | README.md = README.md 13 | EndProjectSection 14 | EndProject 15 | Global 16 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 17 | Debug|Any CPU = Debug|Any CPU 18 | Release|Any CPU = Release|Any CPU 19 | EndGlobalSection 20 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 21 | {E6A105EE-D6F5-476E-B261-B93C9BF480CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 22 | {E6A105EE-D6F5-476E-B261-B93C9BF480CA}.Debug|Any CPU.Build.0 = Debug|Any CPU 23 | {E6A105EE-D6F5-476E-B261-B93C9BF480CA}.Release|Any CPU.ActiveCfg = Release|Any CPU 24 | {E6A105EE-D6F5-476E-B261-B93C9BF480CA}.Release|Any CPU.Build.0 = Release|Any CPU 25 | {07176B1F-8C18-445F-9D57-0B4609C3C685}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 26 | {07176B1F-8C18-445F-9D57-0B4609C3C685}.Debug|Any CPU.Build.0 = Debug|Any CPU 27 | {07176B1F-8C18-445F-9D57-0B4609C3C685}.Release|Any CPU.ActiveCfg = Release|Any CPU 28 | {07176B1F-8C18-445F-9D57-0B4609C3C685}.Release|Any CPU.Build.0 = Release|Any CPU 29 | EndGlobalSection 30 | GlobalSection(SolutionProperties) = preSolution 31 | HideSolutionNode = FALSE 32 | EndGlobalSection 33 | GlobalSection(ExtensibilityGlobals) = postSolution 34 | SolutionGuid = {DAC5F2CB-9470-463E-A131-D5F11521901E} 35 | EndGlobalSection 36 | EndGlobal 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Browser Detect for Blazor 2 | This is a browser detect component for [Blazor WebAssembly](https://www.puresourcecode.com/tag/blazor-webassembly/) and [Blazor Server](https://www.puresourcecode.com/tag/blazor-server/) with .NET6. 3 | 4 | > If you need help, info or some device is not detected correctly, leave your message in the [Forum](https://www.puresourcecode.com/forum/browser-detect-for-blazor/). 5 | 6 | Read the full post on [PureSourceCode.com](https://www.puresourcecode.com): 7 | - [Version 1.0.11](https://www.puresourcecode.com/dotnet/blazor/browser-detect-component-for-blazor-2/) 8 | - [First version](https://www.puresourcecode.com/dotnet/blazor/browser-detect-component-for-blazor) 9 | 10 | Now, you can try your component by yourself from the website. [Try it now!](https://browserdetect.puresourcecode.com/) 11 | 12 | ![browsers](https://user-images.githubusercontent.com/9497415/153390277-3c9ef12c-5e4e-488e-bc3b-c02d84da5195.jpg) 13 | 14 | ## Installation 15 | Fist, you have to add the component from [NuGet](https://www.nuget.org/packages/PSC.Blazor.Components.BrowserDetect/). Then, open your `_Imports.razor` and add the following: 16 | 17 | ``` 18 | @using PSC.Blazor.Components.BrowserDetect 19 | ``` 20 | 21 | ## Detected capabilities or properties 22 | 23 | | Property | Value | 24 | | --- | --- | 25 | | BrowserName | Name of the browser | 26 | | BrowserMajor | Major version of the browser | 27 | | BrowserVersion | Version of the browser | 28 | | CPUArchitect | If it is possible, the component detect the CPU architecture of the machine | 29 | | DeviceModel | Device model (if it is possible) | 30 | | DeviceType | Device type (if it is possible) | 31 | | DeviceVendor | Device Vendor (if it is possible) | 32 | | EngineName | Browser engine name | 33 | | EngineVersion | Browser engine version | 34 | | GPURenderer | Type of the GPU renderer | 35 | | GPUVendor | Vendor of the GPU | 36 | | IsDesktop | Detect if the device is a desktop computer | 37 | | IsMobile | Detect if the device is a mobile | 38 | | IsTablet | Detect if the device is a tablet | 39 | | IsAndroid | Detect if the device is an Android device | 40 | | IsIPhone | Detect if the device is an iPhone or iPod | 41 | | IsIPad | Detect if the device is an iPad (any version) | 42 | | IsIPadPro | Detect if the device is an iPad Pro | 43 | | OSName | Detect the operating system | 44 | | OSVersion | Version of the operating system | 45 | | ScreenResolution | Detect the screen resolution | 46 | | TimeZone | Read the time zone | 47 | | UserAgent | The full user agent | 48 | 49 | ### Detecting the operating system and the architecture 50 | 51 | To detect the browser and all the other details from the user, it is not always accurate. For example, if the client has `Windows 11` there is no way to 52 | return the correct version of the operating system. If you have a `MacOS`, again JavaScript can't detect from the `navigator` string any info. 53 | The JavaScript can detect the `OSName`, `Windows` or `MacOS` but not the exact version. 54 | 55 | To detect in a correct way the correct version of the operating system and the architecture of the CPU, the component has to run few tests that take time. 56 | 57 | For this reason I added 2 events in the component: 58 | 59 | - OSArchitectureUpdate 60 | - OSVersionUpdate 61 | 62 | So, if you want to receive the notification when of the correct version of the operating system and the CPU architecture, the component is like this code: 63 | 64 | ``` 65 | 68 | ``` 69 | 70 | then the functions look like 71 | 72 | ```csharp 73 | public BrowserInfo? Info { get; set; } 74 | public string? OSInfo { get; set; } = ""; 75 | public string? OSCPUInfoString { get; set; } 76 | 77 | private void OSArchitectureString(string cpu) 78 | { 79 | OSCPUInfoString = cpu; 80 | } 81 | 82 | private void OSUpdateString(string version) 83 | { 84 | OSInfo = version; 85 | } 86 | ``` 87 | 88 | Both events return a simple string with the values. For example: 89 | 90 | - if the operating system is `Windows 11`, the `OSUpdateString` receives the string `11`; 91 | - if the operating system is `Windows 10 1809`, the `OSUpdateString` receives the string `10 (1809)` 92 | - if the CPU is 32 bit, the `OSArchitectureString`, receives the string `x86` 93 | 94 | #### Mac Architecture 95 | 96 | - ARM 97 | - ARM 64 98 | 99 | #### Windows Architecture Values 100 | 101 | - x86_64 102 | - x86 103 | - ARM64 104 | - ARM32 105 | 106 | #### Windows Version 107 | 108 | | Version | platformVersion | 109 | |------------|-----------------------------------| 110 | | Win7/8/8.1 | 7/8/8.1 | 111 | | Win10 1507 | 10 (1507) | 112 | | Win10 1511 | 10 (1511) | 113 | | Win10 1607 | 10 (1607) | 114 | | Win10 1703 | 10 (1703) | 115 | | Win10 1709 | 10 (1709) | 116 | | Win10 1803 | 10 (1803) | 117 | | Win10 1809 | 10 (1809) | 118 | | Win10 1903 | 10 (1903 or 10 1909) | 119 | | Win10 1909 | 10 (1903 or 10 1909) | 120 | | Win10 2004 | 10 (2004 or 20H2 or 21H1 or 21H2) | 121 | | Win10 20H2 | 10 (2004 or 20H2 or 21H1 or 21H2) | 122 | | Win10 21H1 | 10 (2004 or 20H2 or 21H1 or 21H2) | 123 | | Win10 21H2 | 10 (2004 or 20H2 or 21H1 or 21H2) | 124 | | Win11 | 11 | 125 | 126 | ## Screenshot 127 | 128 | ### Windows 11 129 | ![image](https://user-images.githubusercontent.com/9497415/224862912-9b85159a-5ae4-416c-a5ff-08ff13723960.png) 130 | 131 | ### Windows 10 132 | ![image](https://user-images.githubusercontent.com/9497415/153378372-4c0d0449-7e70-49ce-9c09-3fd5de2538a7.png) 133 | 134 | ## iPhone 135 | ![image](https://user-images.githubusercontent.com/9497415/153378671-8469e052-a17f-45b2-9d8c-1d9822aa7a19.png) 136 | 137 | ## Android mobile 138 | ![Screenshot_20220210-093352 4335](https://user-images.githubusercontent.com/9497415/153379124-1934e7c9-f9ba-4ed0-8c8c-72831914c377.jpg) 139 | 140 | ## Amazon Fire tablet 141 | ![Screenshot_20220210-093737 4338](https://user-images.githubusercontent.com/9497415/153380151-07f11db7-0ef3-450f-9a9d-a9b34eee0c11.png) 142 | 143 | ## iPad 144 | ![image](https://user-images.githubusercontent.com/9497415/153380116-819ea3bc-1a6f-4d86-9213-24f092fc7372.png) 145 | 146 | ## iMac 147 | Screenshot 2022-02-10 at 09 42 16 148 | 149 | 150 | --- 151 | 152 | ## PureSourceCode.com 153 | 154 | [PureSourceCode.com](https://www.puresourcecode.com/) is my personal blog where I publish posts about technologies and in particular source code and projects in [.NET](https://www.puresourcecode.com/category/dotnet/). 155 | 156 | In the last few months, I created a lot of components for [Blazor WebAssembly](https://www.puresourcecode.com/tag/blazor-webassembly/) and [Blazor Server](https://www.puresourcecode.com/tag/blazor-server/). 157 | 158 | My name is Enrico Rossini and you can contact me via: 159 | - [Personal Twitter](https://twitter.com/erossiniuk) 160 | - [LinkedIn](https://www.linkedin.com/in/rossiniuk) 161 | - [Facebook](https://www.facebook.com/puresourcecode) 162 | 163 | ## Blazor Components 164 | 165 | | Component name | Forum | NuGet | Website | Description | 166 | |---|---|---|---|---| 167 | | [AnchorLink]() | [Forum](https://puresourcecode.com/forum/anchorlink/) | ![NuGet badge](https://img.shields.io/nuget/v/PSC.Blazor.Components.AnchorLink) | | An anchor link is a web link that allows users to leapfrog to a specific point on a website page. It saves them the need to scroll and skim read and makes navigation easier. This component is for [Blazor WebAssembly](https://www.puresourcecode.com/tag/blazor-webassembly/) and [Blazor Server](https://www.puresourcecode.com/tag/blazor-server/) | 168 | | [Autocomplete for Blazor](https://www.puresourcecode.com/dotnet/net-core/autocomplete-component-for-blazor/) | [Forum](https://www.puresourcecode.com/forum/autocomplete-blazor/) | ![NuGet badge](https://img.shields.io/nuget/v/PSC.Blazor.Components.Autocomplete) | | Simple and flexible autocomplete type-ahead functionality for [Blazor WebAssembly](https://www.puresourcecode.com/tag/blazor-webassembly/) and [Blazor Server](https://www.puresourcecode.com/tag/blazor-server/) | 169 | | [Browser Detect for Blazor](https://www.puresourcecode.com/dotnet/blazor/browser-detect-component-for-blazor/) | [Forum](https://www.puresourcecode.com/forum/browser-detect-for-blazor/) | ![NuGet badge](https://img.shields.io/nuget/v/PSC.Blazor.Components.BrowserDetect) | [Demo](https://browserdetect.puresourcecode.com) | Browser detect for Blazor WebAssembly and Blazor Server | 170 | | [ChartJs for Blazor](https://www.puresourcecode.com/dotnet/blazor/blazor-component-for-chartjs/) | [Forum](https://www.puresourcecode.com/forum/chart-js-for-blazor/) | ![NuGet badge](https://img.shields.io/nuget/v/PSC.Blazor.Components.Chartjs) | [Demo](https://chartjs.puresourcecode.com/) | Add beautiful graphs based on ChartJs in your Blazor application | 171 | | [Clippy for Blazor](https://www.puresourcecode.com/dotnet/blazor/blazor-component-for-chartjs/) | [Forum](https://www.puresourcecode.com/forum/clippy/) | ![NuGet badge](https://img.shields.io/nuget/v/PSC.Blazor.Components.Clippy) | [Demo](https://clippy.puresourcecode.com/) | Do you miss Clippy? Here the implementation for Blazor | 172 | | [CodeSnipper for Blazor](https://www.puresourcecode.com/dotnet/blazor/code-snippet-component-for-blazor/) | [Forum](https://www.puresourcecode.com/forum/codesnippet-for-blazor/) | ![NuGet badge](https://img.shields.io/nuget/v/PSC.Blazor.Components.CodeSnippet) | | Add code snippet in your Blazor pages for 196 programming languages with 243 styles | 173 | | [Copy To Clipboard](https://www.puresourcecode.com/dotnet/blazor/copy-to-clipboard-component-for-blazor/) | [Forum](https://www.puresourcecode.com/forum/copytoclipboard/) | ![NuGet badge](https://img.shields.io/nuget/v/PSC.Blazor.Components.CopyToClipboard) | | Add a button to copy text in the clipboard | 174 | | [DataTable for Blazor](https://www.puresourcecode.com/dotnet/net-core/datatable-component-for-blazor/) | [Forum](https://www.puresourcecode.com/forum/forum/datatables/) | ![NuGet badge](https://img.shields.io/nuget/v/PSC.Blazor.Components.DataTable) | [Demo](https://datatable.puresourcecode.com/) | DataTable component for Blazor WebAssembly and Blazor Server | 175 | | [Google Tag Manager]() | [Forum]() | ![NuGet badge](https://img.shields.io/nuget/v/PSC.Blazor.Components.GoogleTagManager) | [Demo](https://datatable.puresourcecode.com/) | Adds Google Tag Manager to the application and manages communication with GTM JavaScript (data layer). | 176 | | [Icons and flags for Blazor](https://www.puresourcecode.com/forum/icons-and-flags-for-blazor/) | [Forum](https://www.puresourcecode.com/forum/icons-and-flags-for-blazor/) | ![NuGet badge](https://img.shields.io/nuget/v/PSC.Blazor.Components.Icons) | | Library with a lot of SVG icons and SVG flags to use in your Razor pages | 177 | | [ImageSelect for Blazor]() | [Forum](https://puresourcecode.com/forum/imageselect/) | ![NuGet badge](https://img.shields.io/nuget/v/PSC.Blazor.Components.ImageSelect) | | This is a Blazor component to display a dropdown list with images based on ms-Dropdown by Marghoob Suleman. This component is built with NET7 for [Blazor WebAssembly](https://www.puresourcecode.com/tag/blazor-webassembly/) and [Blazor Server](https://www.puresourcecode.com/tag/blazor-server/) | 178 | | [Markdown editor for Blazor](https://www.puresourcecode.com/dotnet/blazor/markdown-editor-with-blazor/) | [Forum](https://www.puresourcecode.com/forum/forum/markdown-editor-for-blazor/) | ![NuGet badge](https://img.shields.io/nuget/v/PSC.Blazor.Components.MarkdownEditor) | [Demo](https://markdown.puresourcecode.com/) | This is a Markdown Editor for use in Blazor. It contains a live preview as well as an embeded help guide for users. | 179 | | [Modal dialog for Blazor](https://www.puresourcecode.com/dotnet/blazor/modal-dialog-component-for-blazor/) | [Forum](https://www.puresourcecode.com/forum/forum/modal-dialog-for-blazor/) | ![NuGet badge](https://img.shields.io/nuget/v/PSC.Blazor.Components.ModalDialog) | | Simple Modal Dialog for Blazor WebAssembly | 180 | | [Modal windows for Blazor](https://www.puresourcecode.com/dotnet/blazor/modal-dialog-component-for-blazor/) | [Forum](https://www.puresourcecode.com/forum/forum/modal-dialog-for-blazor/) | ![NuGet badge](https://img.shields.io/nuget/v/PSC.Blazor.Components.Modals) | | Modal Windows for Blazor WebAssembly | 181 | | [Quill for Blazor](https://www.puresourcecode.com/dotnet/blazor/create-a-blazor-component-for-quill/) | [Forum](https://www.puresourcecode.com/forum/forum/quill-for-blazor/) | ![NuGet badge](https://img.shields.io/nuget/v/PSC.Blazor.Components.Quill) | | Quill Component is a custom reusable control that allows us to easily consume Quill and place multiple instances of it on a single page in our Blazor application | 182 | | [ScrollTabs](https://www.puresourcecode.com/dotnet/blazor/scrolltabs-component-for-blazor/) | | ![NuGet badge](https://img.shields.io/nuget/v/PSC.Blazor.Components.ScrollTabs) | | Tabs with nice scroll (no scrollbar) and responsive | 183 | | [Segment for Blazor](https://www.puresourcecode.com/dotnet/blazor/segment-control-for-blazor/) | [Forum](https://www.puresourcecode.com/forum/forum/segments-for-blazor/) | ![NuGet badge](https://img.shields.io/nuget/v/PSC.Blazor.Components.Segments) | | This is a Segment component for Blazor Web Assembly and Blazor Server | 184 | | [Tabs for Blazor](https://www.puresourcecode.com/dotnet/blazor/tabs-control-for-blazor/) | [Forum](https://www.puresourcecode.com/forum/forum/tabs-for-blazor/) | ![NuGet badge](https://img.shields.io/nuget/v/PSC.Blazor.Components.Tabs) | | This is a Tabs component for Blazor Web Assembly and Blazor Server | 185 | | [Timeline for Blazor](https://www.puresourcecode.com/dotnet/blazor/timeline-component-for-blazor/) | [Forum](https://www.puresourcecode.com/forum/timeline/) | ![NuGet badge](https://img.shields.io/nuget/v/PSC.Blazor.Components.Timeline) | | This is a new responsive timeline for Blazor Web Assembly and Blazor Server | 186 | | [Toast for Blazor](https://www.puresourcecode.com/forum/psc-components-and-source-code/) | [Forum](https://www.puresourcecode.com/forum/psc-components-and-source-code/) | ![NuGet badge](https://img.shields.io/nuget/v/PSC.Blazor.Components.Toast) | | Toast notification for Blazor applications | 187 | | [Tours for Blazor](https://www.puresourcecode.com/forum/psc-components-and-source-code/) | [Forum](https://www.puresourcecode.com/forum/psc-components-and-source-code/) | ![NuGet badge](https://img.shields.io/nuget/v/PSC.Blazor.Components.Tours) | | Guide your users in your Blazor applications | 188 | | [TreeView for Blazor]() | [Forum](https://puresourcecode.com/forum/treeview/) | ![NuGet badge](https://img.shields.io/nuget/v/PSC.Blazor.Components.TreeView) | | This component is a native Blazor TreeView component for [Blazor WebAssembly](https://www.puresourcecode.com/tag/blazor-webassembly/) and [Blazor Server](https://www.puresourcecode.com/tag/blazor-server/). The component is built with .NET7. | 189 | | [WorldMap for Blazor](https://puresourcecode.com/dotnet/blazor/world-map-component-for-blazor) | [Forum](https://www.puresourcecode.com/forum/worldmap-for-blazor/) | ![NuGet badge](https://img.shields.io/nuget/v/PSC.Blazor.Components.WorldMap) | [Demo](https://worldmap.puresourcecode.com/) | Show world maps with your data | 190 | 191 | ## C# libraries for .NET6 192 | 193 | | Component name | Forum | NuGet | Description | 194 | |---|---|---|---| 195 | | [PSC.Evaluator](https://www.puresourcecode.com/forum/psc-components-and-source-code/) | [Forum](https://www.puresourcecode.com/forum/forum/psc-extensions/) | ![NuGet badge](https://img.shields.io/nuget/v/PSC.Evaluator) | PSC.Evaluator is a mathematical expressions evaluator library written in C#. Allows to evaluate mathematical, boolean, string and datetime expressions. | 196 | | [PSC.Extensions](https://www.puresourcecode.com/dotnet/net-core/a-lot-of-functions-for-net5/) | [Forum](https://www.puresourcecode.com/forum/forum/psc-extensions/) | ![NuGet badge](https://img.shields.io/nuget/v/PSC.Extensions) | A lot of functions for .NET5 in a NuGet package that you can download for free. We collected in this package functions for everyday work to help you with claim, strings, enums, date and time, expressions... | 197 | 198 | ## More examples and documentation 199 | 200 | ### Blazor 201 | * [Write a reusable Blazor component](https://www.puresourcecode.com/dotnet/blazor/write-a-reusable-blazor-component/) 202 | * [Getting Started With C# And Blazor](https://www.puresourcecode.com/dotnet/net-core/getting-started-with-c-and-blazor/) 203 | * [Setting Up A Blazor WebAssembly Application](https://www.puresourcecode.com/dotnet/blazor/setting-up-a-blazor-webassembly-application/) 204 | * [Working With Blazor Component Model](https://www.puresourcecode.com/dotnet/blazor/working-with-blazors-component-model/) 205 | * [Secure Blazor WebAssembly With IdentityServer4](https://www.puresourcecode.com/dotnet/blazor/secure-blazor-webassembly-with-identityserver4/) 206 | * [Blazor Using HttpClient With Authentication](https://www.puresourcecode.com/dotnet/blazor/blazor-using-httpclient-with-authentication/) 207 | * [InputSelect component for enumerations in Blazor](https://www.puresourcecode.com/dotnet/blazor/inputselect-component-for-enumerations-in-blazor/) 208 | * [Use LocalStorage with Blazor WebAssembly](https://www.puresourcecode.com/dotnet/blazor/use-localstorage-with-blazor-webassembly/) 209 | * [Modal Dialog component for Blazor](https://www.puresourcecode.com/dotnet/blazor/modal-dialog-component-for-blazor/) 210 | * [Create Tooltip component for Blazor](https://www.puresourcecode.com/dotnet/blazor/create-tooltip-component-for-blazor/) 211 | * [Consume ASP.NET Core Razor components from Razor class libraries | Microsoft Docs](https://docs.microsoft.com/en-us/aspnet/core/blazor/components/class-libraries?view=aspnetcore-5.0&tabs=visual-studio) 212 | * [ChartJs component for Blazor](https://www.puresourcecode.com/dotnet/blazor/blazor-component-for-chartjs/) 213 | * [Labels and OnClickChart for ChartJs](https://www.puresourcecode.com/dotnet/blazor/labels-and-onclickchart-for-chartjs/) 214 | 215 | ### Blazor & NET8 216 | * [Custom User Management with NET8 and Blazor (1st part)](https://puresourcecode.com/dotnet/blazor/custom-user-management-with-net8-and-blazor/) 217 | * [NET8, Blazor and Custom User Management (2nd part)](https://puresourcecode.com/dotnet/blazor/net8-blazor-and-custom-user-management/) 218 | -------------------------------------------------------------------------------- /src/BrowserDetect.razor: -------------------------------------------------------------------------------- 1 | @inject IJSRuntime JSRuntime 2 | 3 | @code { 4 | #region Inject and JavaScript 5 | 6 | /// 7 | /// The dotnet object reference 8 | /// 9 | private DotNetObjectReference? dotNetObjectRef; 10 | 11 | #endregion 12 | #region Parameters 13 | [Parameter] 14 | public BrowserInfo? browserInfo { get; set; } 15 | 16 | [Parameter] 17 | public EventCallback BrowserInfoChanged { get; set; } 18 | 19 | [Parameter] 20 | public EventCallback OSVersionUpdate { get; set; } 21 | 22 | [Parameter] 23 | public EventCallback OSArchitectureUpdate { get; set; } 24 | #endregion 25 | 26 | protected BrowserDetectJsInterop? JSModule { get; private set; } 27 | 28 | protected override async Task OnAfterRenderAsync(bool firstRender) 29 | { 30 | if (firstRender) 31 | { 32 | JSModule = new BrowserDetectJsInterop(JSRuntime); 33 | 34 | dotNetObjectRef ??= DotNetObjectReference.Create(this); 35 | 36 | browserInfo = await JSModule.BrowserInfo(dotNetObjectRef); 37 | await BrowserInfoChanged.InvokeAsync(browserInfo); 38 | } 39 | } 40 | 41 | [JSInvokable] 42 | public async Task OSUpdateString(string version) 43 | { 44 | await OSVersionUpdate.InvokeAsync(version); 45 | await InvokeAsync(StateHasChanged); 46 | } 47 | 48 | [JSInvokable] 49 | public async Task OSArchitectureUpdateString(string cpu) 50 | { 51 | await OSArchitectureUpdate.InvokeAsync(cpu); 52 | await InvokeAsync(StateHasChanged); 53 | } 54 | } -------------------------------------------------------------------------------- /src/BrowserDetectJsInterop.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.JSInterop; 2 | 3 | namespace PSC.Blazor.Components.BrowserDetect 4 | { 5 | // This class provides an example of how JavaScript functionality can be wrapped 6 | // in a .NET class for easy consumption. The associated JavaScript module is 7 | // loaded on demand when first needed. 8 | // 9 | // This class can be registered as scoped DI service and then injected into Blazor 10 | // components for use. 11 | 12 | public class BrowserDetectJsInterop : IAsyncDisposable 13 | { 14 | private readonly Lazy> moduleTask; 15 | 16 | /// 17 | /// Initializes a new instance of the class. 18 | /// 19 | /// The js runtime. 20 | public BrowserDetectJsInterop(IJSRuntime jsRuntime) 21 | { 22 | moduleTask = new(() => jsRuntime.InvokeAsync("import", 23 | "./_content/PSC.Blazor.Components.BrowserDetect/js/browserDetect.js").AsTask()); 24 | } 25 | 26 | /// 27 | /// Browsers the information. 28 | /// 29 | /// The dot net object reference. 30 | /// BrowserInfo. 31 | public async ValueTask BrowserInfo(DotNetObjectReference dotNetObjectRef) 32 | { 33 | var module = await moduleTask.Value; 34 | return await module.InvokeAsync("browserDetect", dotNetObjectRef); 35 | } 36 | 37 | /// 38 | /// Dispose as an asynchronous operation. 39 | /// 40 | /// A Task<ValueTask> representing the asynchronous operation. 41 | public async ValueTask DisposeAsync() 42 | { 43 | if (moduleTask.IsValueCreated) 44 | { 45 | var module = await moduleTask.Value; 46 | await module.DisposeAsync(); 47 | } 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /src/Models/BrowserDetect.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace PSC.Blazor.Components.BrowserDetect 8 | { 9 | /// 10 | /// Class BrowserInfo. 11 | /// 12 | public class BrowserInfo 13 | { 14 | /// 15 | /// Gets or sets the browser major. 16 | /// 17 | /// The browser major. 18 | public string? BrowserMajor { get; set; } 19 | /// 20 | /// Gets or sets the name of the browser. 21 | /// 22 | /// The name of the browser. 23 | public string? BrowserName { get; set; } 24 | /// 25 | /// Gets or sets the browser version. 26 | /// 27 | /// The browser version. 28 | public string? BrowserVersion { get; set; } 29 | /// 30 | /// Gets or sets the cpu architect. 31 | /// 32 | /// The cpu architect. 33 | public string? CPUArchitect { get; set; } 34 | /// 35 | /// Gets or sets the device model. 36 | /// 37 | /// The device model. 38 | public string? DeviceModel { get; set; } 39 | /// 40 | /// Gets or sets the type of the device. 41 | /// 42 | /// The type of the device. 43 | public string? DeviceType { get; set; } 44 | /// 45 | /// Gets or sets the device vendor. 46 | /// 47 | /// The device vendor. 48 | public string? DeviceVendor { get; set; } 49 | /// 50 | /// Gets or sets the name of the engine. 51 | /// 52 | /// The name of the engine. 53 | public string? EngineName { get; set; } 54 | /// 55 | /// Gets or sets the engine version. 56 | /// 57 | /// The engine version. 58 | public string? EngineVersion { get; set; } 59 | /// 60 | /// Gets or sets the gpu renderer. 61 | /// 62 | /// The gpu renderer. 63 | public string? GPURenderer { get; set; } 64 | /// 65 | /// Gets or sets the gpu vendor. 66 | /// 67 | /// The gpu vendor. 68 | public string? GPUVendor { get; set; } 69 | /// 70 | /// Gets or sets the ip. 71 | /// 72 | /// The ip. 73 | public string? IP { get; set; } 74 | /// 75 | /// Gets or sets a value indicating whether this instance is android. 76 | /// 77 | /// null if [is android] contains no value, true if [is android]; otherwise, false. 78 | public bool? IsAndroid { get; set; } 79 | /// 80 | /// Gets or sets a value indicating whether this instance is desktop. 81 | /// 82 | /// null if [is desktop] contains no value, true if [is desktop]; otherwise, false. 83 | public bool? IsDesktop { get; set; } 84 | /// 85 | /// Gets or sets a value indicating whether this instance is i pad. 86 | /// 87 | /// null if [is i pad] contains no value, true if [is i pad]; otherwise, false. 88 | public bool? IsIPad { get; set; } 89 | /// 90 | /// Gets or sets a value indicating whether this instance is i pad pro. 91 | /// 92 | /// null if [is i pad pro] contains no value, true if [is i pad pro]; otherwise, false. 93 | public bool? IsIPadPro { get; set; } 94 | /// 95 | /// Gets or sets a value indicating whether this instance is i phone. 96 | /// 97 | /// null if [is i phone] contains no value, true if [is i phone]; otherwise, false. 98 | public bool? IsIPhone { get; set; } 99 | /// 100 | /// Gets or sets a value indicating whether this instance is mobile. 101 | /// 102 | /// null if [is mobile] contains no value, true if [is mobile]; otherwise, false. 103 | public bool? IsMobile { get; set; } 104 | /// 105 | /// Gets or sets a value indicating whether this instance is tablet. 106 | /// 107 | /// null if [is tablet] contains no value, true if [is tablet]; otherwise, false. 108 | public bool? IsTablet { get; set; } 109 | /// 110 | /// Gets or sets the name of the os. 111 | /// 112 | /// The name of the os. 113 | public string? OSName { get; set; } 114 | /// 115 | /// Gets or sets the os version. 116 | /// 117 | /// The os version. 118 | public string? OSVersion { get; set; } 119 | /// 120 | /// Gets or sets the screen resolution. 121 | /// 122 | /// The screen resolution. 123 | public string? ScreenResolution { get; set; } 124 | /// 125 | /// Gets or sets the time zone. 126 | /// 127 | /// The time zone. 128 | public string? TimeZone { get; set; } 129 | /// 130 | /// Gets or sets the user agent. 131 | /// 132 | /// The user agent. 133 | public string? UserAgent { get; set; } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/PSC.Blazor.Components.BrowserDetect.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | enable 6 | enable 7 | psc_logo.ico 8 | True 9 | Browser Detect for Blazor 10 | psc_logo.png 11 | README.md 12 | git 13 | Enrico Rossini - PureSourceCode.com 14 | https://www.puresourcecode.com/dotnet/blazor/browser-detect-component-for-blazor-2/ 15 | https://github.com/erossini/BlazorBrowserDetect 16 | blazor, blazor-webassembly, blazor-server, browser, detect, 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | True 26 | \ 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | True 42 | \ 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /src/_Imports.razor: -------------------------------------------------------------------------------- 1 | @using Microsoft.AspNetCore.Components.Web 2 | @using Microsoft.JSInterop -------------------------------------------------------------------------------- /src/psc_logo.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erossini/BlazorBrowserDetect/517dd91f890a6503061ccdfff252382a0b63c405/src/psc_logo.ico -------------------------------------------------------------------------------- /src/psc_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erossini/BlazorBrowserDetect/517dd91f890a6503061ccdfff252382a0b63c405/src/psc_logo.png -------------------------------------------------------------------------------- /src/wwwroot/js/browserDetect.js: -------------------------------------------------------------------------------- 1 | export function browserDetect(dotNetObjectRef) { 2 | var parser = new UAParser(); 3 | var result = parser.getResult(); 4 | 5 | var gl = getGlRenderer(); 6 | 7 | var isMobile = navigator.userAgent.toLowerCase().indexOf('mobile') > -1 ?? (isiOS()), 8 | isTablet = navigator.userAgent.toLowerCase().indexOf('tablet') > -1 ?? (isiPadOS() || isiPadPro()), 9 | isAndroid = navigator.userAgent.toLowerCase().indexOf('android') > -1, 10 | isiPhone = isiOS(), 11 | isiPad = isiPadOS() || isiPadPro(); 12 | 13 | var agent = window.navigator.userAgent; 14 | var timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone; 15 | 16 | var deviceVendor = result.device.vendor ?? ''; 17 | if (deviceVendor === '' && (isiPad || isiPhone || isiPadPro())) 18 | deviceVendor = 'Apple Inc.'; 19 | 20 | var deviceModel = result.device.model ?? ''; 21 | var deviceType = result.device.type ?? ''; 22 | 23 | console.log('Device model ->' + deviceModel); 24 | if (deviceModel === 'iPad' || isiPad) { 25 | deviceType = 'iPad'; 26 | deviceModel = getModels().toString(); 27 | } 28 | else if (deviceModel === 'iPhone' || isiPhone) { 29 | deviceType = 'iPhone'; 30 | deviceModel = getModels().toString(); 31 | } 32 | 33 | var osName = parser.getOS().name; 34 | var osVersion = parser.getOS().version; 35 | 36 | var winVer = ''; 37 | 38 | if (typeof navigator.userAgentData !== 'undefined') { 39 | navigator.userAgentData.getHighEntropyValues(["platformVersion"]) 40 | .then(ua => { 41 | if (navigator.userAgentData.platform === "Windows") { 42 | var majorPlatformVersion = parseInt(ua.platformVersion.split('.')[0]); 43 | if (majorPlatformVersion >= 13) { 44 | winVer = "11 or later"; 45 | } 46 | else { 47 | switch (majorPlatformVersion) { 48 | case 0: 49 | winVer = "7/8/8.1"; 50 | break; 51 | case 1: 52 | winVer = "10 (1507)"; 53 | break; 54 | case 2: 55 | winVer = "10 (1511)"; 56 | break; 57 | case 3: 58 | winVer = "10 (1607)"; 59 | break; 60 | case 4: 61 | winVer = "10 (1703)"; 62 | break; 63 | case 5: 64 | winVer = "10 (1709)"; 65 | break; 66 | case 6: 67 | winVer = "10 (1803)"; 68 | break; 69 | case 7: 70 | winVer = "10 (1809)"; 71 | break; 72 | case 8: 73 | winVer = "10 (1903 or 10 1909)"; 74 | break; 75 | case 10: 76 | winVer = "10 (2004 or 20H2 or 21H1 or 21H2)"; 77 | break; 78 | default: 79 | break; 80 | } 81 | } 82 | 83 | osVersion = winVer; 84 | 85 | dotNetObjectRef.invokeMethodAsync('OSUpdateString', winVer).then(null, function (err) { 86 | throw new Error(err); 87 | }); 88 | } 89 | else if (navigator.userAgentData.platform === "macOS") { 90 | var macVer = ua.platformVersion; 91 | dotNetObjectRef.invokeMethodAsync('OSUpdateString', macVer).then(null, function (err) { 92 | throw new Error(err); 93 | }); 94 | } 95 | }); 96 | 97 | navigator.userAgentData.getHighEntropyValues(["architecture", "bitness"]) 98 | .then(ua => { 99 | if (navigator.userAgentData.platform === "Windows") { 100 | var winCPU = ''; 101 | if (ua.architecture === 'x86') { 102 | if (ua.bitness === '64') { 103 | winCPU = "x86_64"; 104 | } 105 | else if (ua.bitness === '32') { 106 | winCPU = "x86"; 107 | } 108 | } 109 | else if (ua.architecture === 'arm') { 110 | if (ua.bitness === '64') { 111 | winCPU = "ARM64"; 112 | } 113 | else if (ua.bitness === '32') { 114 | winCPU = "ARM32"; 115 | } 116 | } 117 | 118 | dotNetObjectRef.invokeMethodAsync('OSArchitectureUpdateString', winCPU).then(null, function (err) { 119 | throw new Error(err); 120 | }); 121 | } 122 | else if (navigator.userAgentData.platform === "macOS") { 123 | var macCPU = ua.architecture + ' ' + ua.bitness; 124 | dotNetObjectRef.invokeMethodAsync('OSArchitectureUpdateString', macCPU).then(null, function (err) { 125 | throw new Error(err); 126 | }); 127 | } 128 | }); 129 | } 130 | 131 | var rtn = { 132 | BrowserMajor: result.browser.major ?? '', 133 | BrowserName: result.browser.name ?? '', 134 | BrowserVersion: result.browser.version ?? '', 135 | CPUArchitect: result.cpu.architecture ?? '', 136 | DeviceModel: deviceModel, 137 | DeviceType: deviceType, 138 | DeviceVendor: deviceVendor, 139 | EngineName: result.engine.name ?? '', 140 | EngineVersion: result.engine.version ?? '', 141 | GPURenderer: gl.glRenderer, 142 | GPUVendor: gl.glVendor, 143 | IsDesktop: isDesktop(), 144 | IsMobile: isMobile ?? false, 145 | IsTablet: isTablet ?? false, 146 | IsAndroid: isAndroid ?? false, 147 | IsIPhone: isiPhone ?? false, 148 | IsIPad: isiPad ?? false, 149 | isIpadPro: isiPadPro(), 150 | OSName: osName, 151 | OSVersion: osVersion, 152 | ScreenResolution: (window.screen.width * window.devicePixelRatio) + 'x' + (window.screen.height * window.devicePixelRatio), 153 | TimeZone: timeZone, 154 | UserAgent: agent 155 | }; 156 | 157 | return rtn; 158 | } 159 | 160 | function isiOS() { 161 | if (/iPhone|iPod/.test(navigator.platform)) { 162 | return true; 163 | } else { 164 | return false; 165 | } 166 | } 167 | 168 | function isiPadOS() { 169 | if (/iPad/.test(navigator.platform)) { 170 | return true; 171 | } else { 172 | return false; 173 | } 174 | } 175 | 176 | function isiPadPro() { 177 | var ratio = window.devicePixelRatio || 1; 178 | var screen = { 179 | width: window.screen.width * ratio, 180 | height: window.screen.height * ratio 181 | }; 182 | 183 | return (screen.width === 2048 && screen.height === 2732) || 184 | (screen.width === 2732 && screen.height === 2048) || 185 | (screen.width === 1536 && screen.height === 2048) || 186 | (screen.width === 2048 && screen.height === 1536); 187 | } 188 | 189 | function isDesktop() { 190 | if ((navigator.userAgent.match(/iPhone/i)) || 191 | (navigator.userAgent.match(/(up.browser|up.link|mmp|symbian|smartphone|midp|wap|vodafone|o2|pocket|kindle|mobile|pda|psp|treo)/i)) || 192 | (navigator.userAgent.match(/iPod/i)) || 193 | (navigator.userAgent.match(/operamini/i)) || 194 | (navigator.userAgent.match(/blackberry/i)) || 195 | (navigator.userAgent.match(/(palmos|palm|hiptop|avantgo|plucker|xiino|blazer|elaine)/i)) || 196 | (navigator.userAgent.match(/(windowsce; ppc;|windows ce;smartphone;|windows ce; iemobile) /i)) || 197 | (navigator.userAgent.match(/android/i)) || isiOS() || isiPadOS() || isiPadPro()) { 198 | return false; 199 | } 200 | else { 201 | return true; 202 | } 203 | } 204 | 205 | function OSVersion() { 206 | var rtn = ""; 207 | 208 | var userAgent = window.navigator.userAgent; 209 | 210 | if (userAgent.indexOf("Windows NT 10.0") > 0) { 211 | rtn = "Windows 10/11"; 212 | } 213 | else if (userAgent.indexOf("Windows NT 6.3") > 0) { 214 | rtn = "Windows 8.1"; 215 | } 216 | else if (userAgent.indexOf("Windows NT 6.2") > 0) { 217 | rtn = "Windows 8"; 218 | } 219 | else if (userAgent.indexOf("Windows NT 6.1") > 0) { 220 | rtn = "Windows 7"; 221 | } 222 | else if (userAgent.indexOf("Windows NT 6.0") > 0) { 223 | rtn = "Windows Vista"; 224 | } 225 | else if (userAgent.indexOf("Windows NT 5.2") > 0) { 226 | rtn = "Windows Server 2003; Windows XP x64 Edition"; 227 | } 228 | else if (userAgent.indexOf("Windows NT 5.1") > 0) { 229 | rtn = "Windows XP"; 230 | } 231 | else if (userAgent.indexOf("Windows NT 5.01") > 0) { 232 | rtn = "Windows 2000, Service Pack 1 (SP1)"; 233 | } 234 | else if (userAgent.indexOf("Windows NT 5.0") > 0) { 235 | rtn = "Windows 2000"; 236 | } 237 | else if (userAgent.indexOf("Windows NT 4.0") > 0) { 238 | rtn = "Microsoft Windows NT 4.0"; 239 | } 240 | else if (userAgent.indexOf("Win 9x 4.90") > 0) { 241 | rtn = "Windows Millennium Edition (Windows Me)"; 242 | } 243 | else if (userAgent.indexOf("Windows 98") > 0) { 244 | rtn = "Windows 98"; 245 | } 246 | else if (userAgent.indexOf("Windows 95") > 0) { 247 | rtn = "Windows 95"; 248 | } 249 | else if (userAgent.indexOf("Windows CE") > 0) { 250 | rtn = "Windows CE"; 251 | } 252 | else if (userAgent.indexOf("iPhone OS") > 0) { 253 | rtn = "iPhone OS"; 254 | } 255 | else if (userAgent.indexOf("Mac OS") > 0) { 256 | rtn = "Max OS"; 257 | } 258 | else if (userAgent.indexOf("Android") > 0) { 259 | rtn = "Android"; 260 | } 261 | else if (userAgent.indexOf("Silk") > 0) { 262 | rtn = "Amazon Fire"; 263 | } 264 | else if (userAgent.indexOf("facebook") > 0) { 265 | rtn = "Facebook External Hit"; 266 | } 267 | else if (userAgent.indexOf("Twitterbot") > 0) { 268 | rtn = "Twitterbot"; 269 | } 270 | else if (userAgent.indexOf("WhatsApp") > 0) { 271 | rtn = "WhatsApp"; 272 | } 273 | else { 274 | //Others 275 | } 276 | 277 | return rtn; 278 | } 279 | 280 | var canvas, gl, glRenderer, models, 281 | devices = [ 282 | ['a7', '640x1136', ['iPhone 5', 'iPhone 5s']], 283 | ['a7', '1536x2048', ['iPad Air', 'iPad Mini 2', 'iPad Mini 3', 'iPad Pro 9.7']], 284 | ['a8', '640x1136', ['iPod touch (6th gen)']], 285 | ['a8', '750x1334', ['iPhone 6']], 286 | ['a8', '1242x2208', ['iPhone 6 Plus']], 287 | ['a8', '1536x2048', ['iPad Air 2', 'iPad Mini 4']], 288 | ['a9', '640x1136', ['iPhone SE']], 289 | ['a9', '750x1334', ['iPhone 6s']], 290 | ['a9', '1242x2208', ['iPhone 6s Plus']], 291 | ['a9x', '1536x2048', ['iPad Pro (1st gen 9.7-inch)']], 292 | ['a9x', '2048x2732', ['iPad Pro (1st gen 12.9-inch)']], 293 | ['a10', '750x1334', ['iPhone 7']], 294 | ['a10', '1242x2208', ['iPhone 7 Plus']], 295 | ['a10x', '1668x2224', ['iPad Pro (2th gen 10.5-inch)']], 296 | ['a10x', '2048x2732', ['iPad Pro (2th gen 12.9-inch)']], 297 | ['a11', '750x1334', ['iPhone 8']], 298 | ['a11', '1242x2208', ['iPhone 8 Plus']], 299 | ['a11', '1125x2436', ['iPhone X']], 300 | ['a12', '828x1792', ['iPhone Xr']], 301 | ['a12', '1125x2436', ['iPhone Xs']], 302 | ['a12', '1242x2688', ['iPhone Xs Max']], 303 | ['a12x', '1668x2388', ['iPad Pro (3rd gen 11-inch)']], 304 | ['a12x', '2048x2732', ['iPad Pro (3rd gen 12.9-inch)']], 305 | ['a15', '2556x1179', ['iPhone 14']], 306 | ['a15', '2796x1290', ['iPhone 14 Max']] 307 | ]; 308 | 309 | function getCanvas() { 310 | if (canvas == null) { 311 | canvas = document.createElement('canvas'); 312 | } 313 | 314 | return canvas; 315 | } 316 | 317 | function getGl() { 318 | if (gl == null) { 319 | gl = getCanvas().getContext('webgl'); 320 | } 321 | 322 | return gl; 323 | } 324 | 325 | function getResolution() { 326 | var ratio = window.devicePixelRatio || 1; 327 | return (Math.min(screen.width, screen.height) * ratio) 328 | + 'x' + (Math.max(screen.width, screen.height) * ratio); 329 | } 330 | 331 | function getGlRenderer() { 332 | var glVendor = 'Unknown'; 333 | 334 | if (glRenderer == null) { 335 | const gl = document.createElement("canvas").getContext("webgl"); 336 | // try to get the extensions 337 | const ext = gl.getExtension("WEBGL_debug_renderer_info"); 338 | 339 | // if the extension exists, find out the info. 340 | if (ext) { 341 | glRenderer = gl.getParameter(ext.UNMASKED_RENDERER_WEBGL); 342 | glVendor = gl.getParameter(ext.UNMASKED_VENDOR_WEBGL); 343 | } 344 | else { 345 | glRenderer = "Unknown"; 346 | } 347 | } 348 | 349 | return { glRenderer, glVendor }; 350 | } 351 | 352 | function getModels() { 353 | if (models === undefined) { 354 | var gpu = getGlRenderer(); 355 | var matches = gpu.glRenderer.toLowerCase().includes('apple'); 356 | var res = getResolution(); 357 | 358 | models = ['unknown']; 359 | 360 | if (matches) { 361 | for (var i = 0; i < devices.length; i++) { 362 | var device = devices[i]; 363 | 364 | var res2 = res.split('x').reverse().join('x'); 365 | if (res == device[1] || res2 == device[1]) { 366 | models = device[2]; 367 | break; 368 | } 369 | } 370 | } 371 | } 372 | 373 | return models; 374 | } 375 | 376 | function getDeviceInfo() { 377 | if (window.MobileDevice == undefined) { 378 | window.MobileDevice = {}; 379 | } 380 | 381 | window.MobileDevice.getGlRenderer = getGlRenderer; 382 | window.MobileDevice.getModels = getModels; 383 | window.MobileDevice.getResolution = getResolution; 384 | 385 | var currentModels = getModels(); 386 | //var match = match.toLowerCase().replace(/\s+$/, '') + ' '; 387 | 388 | for (var i = 0; i < currentModels.length; i++) { 389 | var model = currentModels[i].toLowerCase() + ' '; 390 | 391 | if (0 === model.indexOf(math)) { 392 | return true; 393 | } 394 | } 395 | } 396 | 397 | (function (window, undefined) { 398 | 'use strict'; 399 | 400 | ////////////// 401 | // Constants 402 | ///////////// 403 | 404 | var LIBVERSION = '0.7.31', 405 | EMPTY = '', 406 | UNKNOWN = '?', 407 | FUNC_TYPE = 'function', 408 | UNDEF_TYPE = 'undefined', 409 | OBJ_TYPE = 'object', 410 | STR_TYPE = 'string', 411 | MAJOR = 'major', 412 | MODEL = 'model', 413 | NAME = 'name', 414 | TYPE = 'type', 415 | VENDOR = 'vendor', 416 | VERSION = 'version', 417 | ARCHITECTURE = 'architecture', 418 | CONSOLE = 'console', 419 | MOBILE = 'mobile', 420 | TABLET = 'tablet', 421 | SMARTTV = 'smarttv', 422 | WEARABLE = 'wearable', 423 | EMBEDDED = 'embedded', 424 | UA_MAX_LENGTH = 275; 425 | 426 | var AMAZON = 'Amazon', 427 | APPLE = 'Apple', 428 | ASUS = 'ASUS', 429 | BLACKBERRY = 'BlackBerry', 430 | BROWSER = 'Browser', 431 | CHROME = 'Chrome', 432 | EDGE = 'Edge', 433 | FIREFOX = 'Firefox', 434 | GOOGLE = 'Google', 435 | HUAWEI = 'Huawei', 436 | LG = 'LG', 437 | MICROSOFT = 'Microsoft', 438 | MOTOROLA = 'Motorola', 439 | OPERA = 'Opera', 440 | SAMSUNG = 'Samsung', 441 | SONY = 'Sony', 442 | XIAOMI = 'Xiaomi', 443 | ZEBRA = 'Zebra', 444 | FACEBOOK = 'Facebook'; 445 | 446 | /////////// 447 | // Helper 448 | ////////// 449 | 450 | var extend = function (regexes, extensions) { 451 | var mergedRegexes = {}; 452 | for (var i in regexes) { 453 | if (extensions[i] && extensions[i].length % 2 === 0) { 454 | mergedRegexes[i] = extensions[i].concat(regexes[i]); 455 | } else { 456 | mergedRegexes[i] = regexes[i]; 457 | } 458 | } 459 | return mergedRegexes; 460 | }, 461 | enumerize = function (arr) { 462 | var enums = {}; 463 | for (var i = 0; i < arr.length; i++) { 464 | enums[arr[i].toUpperCase()] = arr[i]; 465 | } 466 | return enums; 467 | }, 468 | has = function (str1, str2) { 469 | return typeof str1 === STR_TYPE ? lowerize(str2).indexOf(lowerize(str1)) !== -1 : false; 470 | }, 471 | lowerize = function (str) { 472 | return str.toLowerCase(); 473 | }, 474 | majorize = function (version) { 475 | return typeof (version) === STR_TYPE ? version.replace(/[^\d\.]/g, EMPTY).split('.')[0] : undefined; 476 | }, 477 | trim = function (str, len) { 478 | if (typeof (str) === STR_TYPE) { 479 | str = str.replace(/^\s\s*/, EMPTY).replace(/\s\s*$/, EMPTY); 480 | return typeof (len) === UNDEF_TYPE ? str : str.substring(0, UA_MAX_LENGTH); 481 | } 482 | }; 483 | 484 | /////////////// 485 | // Map helper 486 | ////////////// 487 | 488 | var rgxMapper = function (ua, arrays) { 489 | 490 | var i = 0, j, k, p, q, matches, match; 491 | 492 | // loop through all regexes maps 493 | while (i < arrays.length && !matches) { 494 | 495 | var regex = arrays[i], // even sequence (0,2,4,..) 496 | props = arrays[i + 1]; // odd sequence (1,3,5,..) 497 | j = k = 0; 498 | 499 | // try matching uastring with regexes 500 | while (j < regex.length && !matches) { 501 | 502 | matches = regex[j++].exec(ua); 503 | 504 | if (!!matches) { 505 | for (p = 0; p < props.length; p++) { 506 | match = matches[++k]; 507 | q = props[p]; 508 | // check if given property is actually array 509 | if (typeof q === OBJ_TYPE && q.length > 0) { 510 | if (q.length === 2) { 511 | if (typeof q[1] == FUNC_TYPE) { 512 | // assign modified match 513 | this[q[0]] = q[1].call(this, match); 514 | } else { 515 | // assign given value, ignore regex match 516 | this[q[0]] = q[1]; 517 | } 518 | } else if (q.length === 3) { 519 | // check whether function or regex 520 | if (typeof q[1] === FUNC_TYPE && !(q[1].exec && q[1].test)) { 521 | // call function (usually string mapper) 522 | this[q[0]] = match ? q[1].call(this, match, q[2]) : undefined; 523 | } else { 524 | // sanitize match using given regex 525 | this[q[0]] = match ? match.replace(q[1], q[2]) : undefined; 526 | } 527 | } else if (q.length === 4) { 528 | this[q[0]] = match ? q[3].call(this, match.replace(q[1], q[2])) : undefined; 529 | } 530 | } else { 531 | this[q] = match ? match : undefined; 532 | } 533 | } 534 | } 535 | } 536 | i += 2; 537 | } 538 | }, 539 | 540 | strMapper = function (str, map) { 541 | 542 | for (var i in map) { 543 | // check if current value is array 544 | if (typeof map[i] === OBJ_TYPE && map[i].length > 0) { 545 | for (var j = 0; j < map[i].length; j++) { 546 | if (has(map[i][j], str)) { 547 | return (i === UNKNOWN) ? undefined : i; 548 | } 549 | } 550 | } else if (has(map[i], str)) { 551 | return (i === UNKNOWN) ? undefined : i; 552 | } 553 | } 554 | return str; 555 | }; 556 | 557 | /////////////// 558 | // String map 559 | ////////////// 560 | 561 | // Safari < 3.0 562 | var oldSafariMap = { 563 | '1.0': '/8', 564 | '1.2': '/1', 565 | '1.3': '/3', 566 | '2.0': '/412', 567 | '2.0.2': '/416', 568 | '2.0.3': '/417', 569 | '2.0.4': '/419', 570 | '?': '/' 571 | }, 572 | windowsVersionMap = { 573 | 'ME': '4.90', 574 | 'NT 3.11': 'NT3.51', 575 | 'NT 4.0': 'NT4.0', 576 | '2000': 'NT 5.0', 577 | 'XP': ['NT 5.1', 'NT 5.2'], 578 | 'Vista': 'NT 6.0', 579 | '7': 'NT 6.1', 580 | '8': 'NT 6.2', 581 | '8.1': 'NT 6.3', 582 | '10': ['NT 6.4', 'NT 10.0'], 583 | 'RT': 'ARM' 584 | }; 585 | 586 | ////////////// 587 | // Regex map 588 | ///////////// 589 | 590 | var regexes = { 591 | 592 | browser: [[ 593 | 594 | /\b(?:crmo|crios)\/([\w\.]+)/i // Chrome for Android/iOS 595 | ], [VERSION, [NAME, 'Chrome']], [ 596 | /edg(?:e|ios|a)?\/([\w\.]+)/i // Microsoft Edge 597 | ], [VERSION, [NAME, 'Edge']], [ 598 | 599 | // Presto based 600 | /(opera mini)\/([-\w\.]+)/i, // Opera Mini 601 | /(opera [mobiletab]{3,6})\b.+version\/([-\w\.]+)/i, // Opera Mobi/Tablet 602 | /(opera)(?:.+version\/|[\/ ]+)([\w\.]+)/i // Opera 603 | ], [NAME, VERSION], [ 604 | /opios[\/ ]+([\w\.]+)/i // Opera mini on iphone >= 8.0 605 | ], [VERSION, [NAME, OPERA + ' Mini']], [ 606 | /\bopr\/([\w\.]+)/i // Opera Webkit 607 | ], [VERSION, [NAME, OPERA]], [ 608 | 609 | // Mixed 610 | /(kindle)\/([\w\.]+)/i, // Kindle 611 | /(lunascape|maxthon|netfront|jasmine|blazer)[\/ ]?([\w\.]*)/i, // Lunascape/Maxthon/Netfront/Jasmine/Blazer 612 | // Trident based 613 | /(avant |iemobile|slim)(?:browser)?[\/ ]?([\w\.]*)/i, // Avant/IEMobile/SlimBrowser 614 | /(ba?idubrowser)[\/ ]?([\w\.]+)/i, // Baidu Browser 615 | /(?:ms|\()(ie) ([\w\.]+)/i, // Internet Explorer 616 | 617 | // Webkit/KHTML based // Flock/RockMelt/Midori/Epiphany/Silk/Skyfire/Bolt/Iron/Iridium/PhantomJS/Bowser/QupZilla/Falkon 618 | /(flock|rockmelt|midori|epiphany|silk|skyfire|ovibrowser|bolt|iron|vivaldi|iridium|phantomjs|bowser|quark|qupzilla|falkon|rekonq|puffin|brave|whale|qqbrowserlite|qq)\/([-\w\.]+)/i, 619 | // Rekonq/Puffin/Brave/Whale/QQBrowserLite/QQ, aka ShouQ 620 | /(weibo)__([\d\.]+)/i // Weibo 621 | ], [NAME, VERSION], [ 622 | /(?:\buc? ?browser|(?:juc.+)ucweb)[\/ ]?([\w\.]+)/i // UCBrowser 623 | ], [VERSION, [NAME, 'UC' + BROWSER]], [ 624 | /\bqbcore\/([\w\.]+)/i // WeChat Desktop for Windows Built-in Browser 625 | ], [VERSION, [NAME, 'WeChat(Win) Desktop']], [ 626 | /micromessenger\/([\w\.]+)/i // WeChat 627 | ], [VERSION, [NAME, 'WeChat']], [ 628 | /konqueror\/([\w\.]+)/i // Konqueror 629 | ], [VERSION, [NAME, 'Konqueror']], [ 630 | /trident.+rv[: ]([\w\.]{1,9})\b.+like gecko/i // IE11 631 | ], [VERSION, [NAME, 'IE']], [ 632 | /yabrowser\/([\w\.]+)/i // Yandex 633 | ], [VERSION, [NAME, 'Yandex']], [ 634 | /(avast|avg)\/([\w\.]+)/i // Avast/AVG Secure Browser 635 | ], [[NAME, /(.+)/, '$1 Secure ' + BROWSER], VERSION], [ 636 | /\bfocus\/([\w\.]+)/i // Firefox Focus 637 | ], [VERSION, [NAME, FIREFOX + ' Focus']], [ 638 | /\bopt\/([\w\.]+)/i // Opera Touch 639 | ], [VERSION, [NAME, OPERA + ' Touch']], [ 640 | /coc_coc\w+\/([\w\.]+)/i // Coc Coc Browser 641 | ], [VERSION, [NAME, 'Coc Coc']], [ 642 | /dolfin\/([\w\.]+)/i // Dolphin 643 | ], [VERSION, [NAME, 'Dolphin']], [ 644 | /coast\/([\w\.]+)/i // Opera Coast 645 | ], [VERSION, [NAME, OPERA + ' Coast']], [ 646 | /miuibrowser\/([\w\.]+)/i // MIUI Browser 647 | ], [VERSION, [NAME, 'MIUI ' + BROWSER]], [ 648 | /fxios\/([-\w\.]+)/i // Firefox for iOS 649 | ], [VERSION, [NAME, FIREFOX]], [ 650 | /\bqihu|(qi?ho?o?|360)browser/i // 360 651 | ], [[NAME, '360 ' + BROWSER]], [ 652 | /(oculus|samsung|sailfish)browser\/([\w\.]+)/i 653 | ], [[NAME, /(.+)/, '$1 ' + BROWSER], VERSION], [ // Oculus/Samsung/Sailfish Browser 654 | /(comodo_dragon)\/([\w\.]+)/i // Comodo Dragon 655 | ], [[NAME, /_/g, ' '], VERSION], [ 656 | /(electron)\/([\w\.]+) safari/i, // Electron-based App 657 | /(tesla)(?: qtcarbrowser|\/(20\d\d\.[-\w\.]+))/i, // Tesla 658 | /m?(qqbrowser|baiduboxapp|2345Explorer)[\/ ]?([\w\.]+)/i // QQBrowser/Baidu App/2345 Browser 659 | ], [NAME, VERSION], [ 660 | /(metasr)[\/ ]?([\w\.]+)/i, // SouGouBrowser 661 | /(lbbrowser)/i // LieBao Browser 662 | ], [NAME], [ 663 | 664 | // WebView 665 | /((?:fban\/fbios|fb_iab\/fb4a)(?!.+fbav)|;fbav\/([\w\.]+);)/i // Facebook App for iOS & Android 666 | ], [[NAME, FACEBOOK], VERSION], [ 667 | /safari (line)\/([\w\.]+)/i, // Line App for iOS 668 | /\b(line)\/([\w\.]+)\/iab/i, // Line App for Android 669 | /(chromium|instagram)[\/ ]([-\w\.]+)/i // Chromium/Instagram 670 | ], [NAME, VERSION], [ 671 | /\bgsa\/([\w\.]+) .*safari\//i // Google Search Appliance on iOS 672 | ], [VERSION, [NAME, 'GSA']], [ 673 | 674 | /headlesschrome(?:\/([\w\.]+)| )/i // Chrome Headless 675 | ], [VERSION, [NAME, CHROME + ' Headless']], [ 676 | 677 | / wv\).+(chrome)\/([\w\.]+)/i // Chrome WebView 678 | ], [[NAME, CHROME + ' WebView'], VERSION], [ 679 | 680 | /droid.+ version\/([\w\.]+)\b.+(?:mobile safari|safari)/i // Android Browser 681 | ], [VERSION, [NAME, 'Android ' + BROWSER]], [ 682 | 683 | /(chrome|omniweb|arora|[tizenoka]{5} ?browser)\/v?([\w\.]+)/i // Chrome/OmniWeb/Arora/Tizen/Nokia 684 | ], [NAME, VERSION], [ 685 | 686 | /version\/([\w\.]+) .*mobile\/\w+ (safari)/i // Mobile Safari 687 | ], [VERSION, [NAME, 'Mobile Safari']], [ 688 | /version\/([\w\.]+) .*(mobile ?safari|safari)/i // Safari & Safari Mobile 689 | ], [VERSION, NAME], [ 690 | /webkit.+?(mobile ?safari|safari)(\/[\w\.]+)/i // Safari < 3.0 691 | ], [NAME, [VERSION, strMapper, oldSafariMap]], [ 692 | 693 | /(webkit|khtml)\/([\w\.]+)/i 694 | ], [NAME, VERSION], [ 695 | 696 | // Gecko based 697 | /(navigator|netscape\d?)\/([-\w\.]+)/i // Netscape 698 | ], [[NAME, 'Netscape'], VERSION], [ 699 | /mobile vr; rv:([\w\.]+)\).+firefox/i // Firefox Reality 700 | ], [VERSION, [NAME, FIREFOX + ' Reality']], [ 701 | /ekiohf.+(flow)\/([\w\.]+)/i, // Flow 702 | /(swiftfox)/i, // Swiftfox 703 | /(icedragon|iceweasel|camino|chimera|fennec|maemo browser|minimo|conkeror|klar)[\/ ]?([\w\.\+]+)/i, 704 | // IceDragon/Iceweasel/Camino/Chimera/Fennec/Maemo/Minimo/Conkeror/Klar 705 | /(seamonkey|k-meleon|icecat|iceape|firebird|phoenix|palemoon|basilisk|waterfox)\/([-\w\.]+)$/i, 706 | // Firefox/SeaMonkey/K-Meleon/IceCat/IceApe/Firebird/Phoenix 707 | /(firefox)\/([\w\.]+)/i, // Other Firefox-based 708 | /(mozilla)\/([\w\.]+) .+rv\:.+gecko\/\d+/i, // Mozilla 709 | 710 | // Other 711 | /(polaris|lynx|dillo|icab|doris|amaya|w3m|netsurf|sleipnir|obigo|mosaic|(?:go|ice|up)[\. ]?browser)[-\/ ]?v?([\w\.]+)/i, 712 | // Polaris/Lynx/Dillo/iCab/Doris/Amaya/w3m/NetSurf/Sleipnir/Obigo/Mosaic/Go/ICE/UP.Browser 713 | /(links) \(([\w\.]+)/i // Links 714 | ], [NAME, VERSION] 715 | ], 716 | 717 | cpu: [[ 718 | 719 | /(?:(amd|x(?:(?:86|64)[-_])?|wow|win)64)[;\)]/i // AMD64 (x64) 720 | ], [[ARCHITECTURE, 'amd64']], [ 721 | 722 | /(ia32(?=;))/i // IA32 (quicktime) 723 | ], [[ARCHITECTURE, lowerize]], [ 724 | 725 | /((?:i[346]|x)86)[;\)]/i // IA32 (x86) 726 | ], [[ARCHITECTURE, 'ia32']], [ 727 | 728 | /\b(aarch64|arm(v?8e?l?|_?64))\b/i // ARM64 729 | ], [[ARCHITECTURE, 'arm64']], [ 730 | 731 | /\b(arm(?:v[67])?ht?n?[fl]p?)\b/i // ARMHF 732 | ], [[ARCHITECTURE, 'armhf']], [ 733 | 734 | // PocketPC mistakenly identified as PowerPC 735 | /windows (ce|mobile); ppc;/i 736 | ], [[ARCHITECTURE, 'arm']], [ 737 | 738 | /((?:ppc|powerpc)(?:64)?)(?: mac|;|\))/i // PowerPC 739 | ], [[ARCHITECTURE, /ower/, EMPTY, lowerize]], [ 740 | 741 | /(sun4\w)[;\)]/i // SPARC 742 | ], [[ARCHITECTURE, 'sparc']], [ 743 | 744 | /((?:avr32|ia64(?=;))|68k(?=\))|\barm(?=v(?:[1-7]|[5-7]1)l?|;|eabi)|(?=atmel )avr|(?:irix|mips|sparc)(?:64)?\b|pa-risc)/i 745 | // IA64, 68K, ARM/64, AVR/32, IRIX/64, MIPS/64, SPARC/64, PA-RISC 746 | ], [[ARCHITECTURE, lowerize]] 747 | ], 748 | 749 | device: [[ 750 | 751 | ////////////////////////// 752 | // MOBILES & TABLETS 753 | // Ordered by popularity 754 | ///////////////////////// 755 | 756 | // Samsung 757 | /\b(sch-i[89]0\d|shw-m380s|sm-[pt]\w{2,4}|gt-[pn]\d{2,4}|sgh-t8[56]9|nexus 10)/i 758 | ], [MODEL, [VENDOR, SAMSUNG], [TYPE, TABLET]], [ 759 | /\b((?:s[cgp]h|gt|sm)-\w+|galaxy nexus)/i, 760 | /samsung[- ]([-\w]+)/i, 761 | /sec-(sgh\w+)/i 762 | ], [MODEL, [VENDOR, SAMSUNG], [TYPE, MOBILE]], [ 763 | 764 | // Apple 765 | /\((ip(?:hone|od)[\w ]*);/i // iPod/iPhone 766 | ], [MODEL, [VENDOR, APPLE], [TYPE, MOBILE]], [ 767 | /\((ipad);[-\w\),; ]+apple/i, // iPad 768 | /applecoremedia\/[\w\.]+ \((ipad)/i, 769 | /\b(ipad)\d\d?,\d\d?[;\]].+ios/i 770 | ], [MODEL, [VENDOR, APPLE], [TYPE, TABLET]], [ 771 | 772 | // Huawei 773 | /\b((?:ag[rs][23]?|bah2?|sht?|btv)-a?[lw]\d{2})\b(?!.+d\/s)/i 774 | ], [MODEL, [VENDOR, HUAWEI], [TYPE, TABLET]], [ 775 | /(?:huawei|honor)([-\w ]+)[;\)]/i, 776 | /\b(nexus 6p|\w{2,4}-[atu]?[ln][01259x][012359][an]?)\b(?!.+d\/s)/i 777 | ], [MODEL, [VENDOR, HUAWEI], [TYPE, MOBILE]], [ 778 | 779 | // Xiaomi 780 | /\b(poco[\w ]+)(?: bui|\))/i, // Xiaomi POCO 781 | /\b; (\w+) build\/hm\1/i, // Xiaomi Hongmi 'numeric' models 782 | /\b(hm[-_ ]?note?[_ ]?(?:\d\w)?) bui/i, // Xiaomi Hongmi 783 | /\b(redmi[\-_ ]?(?:note|k)?[\w_ ]+)(?: bui|\))/i, // Xiaomi Redmi 784 | /\b(mi[-_ ]?(?:a\d|one|one[_ ]plus|note lte|max|cc)?[_ ]?(?:\d?\w?)[_ ]?(?:plus|se|lite)?)(?: bui|\))/i // Xiaomi Mi 785 | ], [[MODEL, /_/g, ' '], [VENDOR, XIAOMI], [TYPE, MOBILE]], [ 786 | /\b(mi[-_ ]?(?:pad)(?:[\w_ ]+))(?: bui|\))/i // Mi Pad tablets 787 | ], [[MODEL, /_/g, ' '], [VENDOR, XIAOMI], [TYPE, TABLET]], [ 788 | 789 | // OPPO 790 | /; (\w+) bui.+ oppo/i, 791 | /\b(cph[12]\d{3}|p(?:af|c[al]|d\w|e[ar])[mt]\d0|x9007|a101op)\b/i 792 | ], [MODEL, [VENDOR, 'OPPO'], [TYPE, MOBILE]], [ 793 | 794 | // Vivo 795 | /vivo (\w+)(?: bui|\))/i, 796 | /\b(v[12]\d{3}\w?[at])(?: bui|;)/i 797 | ], [MODEL, [VENDOR, 'Vivo'], [TYPE, MOBILE]], [ 798 | 799 | // Realme 800 | /\b(rmx[12]\d{3})(?: bui|;|\))/i 801 | ], [MODEL, [VENDOR, 'Realme'], [TYPE, MOBILE]], [ 802 | 803 | // Motorola 804 | /\b(milestone|droid(?:[2-4x]| (?:bionic|x2|pro|razr))?:?( 4g)?)\b[\w ]+build\//i, 805 | /\bmot(?:orola)?[- ](\w*)/i, 806 | /((?:moto[\w\(\) ]+|xt\d{3,4}|nexus 6)(?= bui|\)))/i 807 | ], [MODEL, [VENDOR, MOTOROLA], [TYPE, MOBILE]], [ 808 | /\b(mz60\d|xoom[2 ]{0,2}) build\//i 809 | ], [MODEL, [VENDOR, MOTOROLA], [TYPE, TABLET]], [ 810 | 811 | // LG 812 | /((?=lg)?[vl]k\-?\d{3}) bui| 3\.[-\w; ]{10}lg?-([06cv9]{3,4})/i 813 | ], [MODEL, [VENDOR, LG], [TYPE, TABLET]], [ 814 | /(lm(?:-?f100[nv]?|-[\w\.]+)(?= bui|\))|nexus [45])/i, 815 | /\blg[-e;\/ ]+((?!browser|netcast|android tv)\w+)/i, 816 | /\blg-?([\d\w]+) bui/i 817 | ], [MODEL, [VENDOR, LG], [TYPE, MOBILE]], [ 818 | 819 | // Lenovo 820 | /(ideatab[-\w ]+)/i, 821 | /lenovo ?(s[56]000[-\w]+|tab(?:[\w ]+)|yt[-\d\w]{6}|tb[-\d\w]{6})/i 822 | ], [MODEL, [VENDOR, 'Lenovo'], [TYPE, TABLET]], [ 823 | 824 | // Nokia 825 | /(?:maemo|nokia).*(n900|lumia \d+)/i, 826 | /nokia[-_ ]?([-\w\.]*)/i 827 | ], [[MODEL, /_/g, ' '], [VENDOR, 'Nokia'], [TYPE, MOBILE]], [ 828 | 829 | // Google 830 | /(pixel c)\b/i // Google Pixel C 831 | ], [MODEL, [VENDOR, GOOGLE], [TYPE, TABLET]], [ 832 | /droid.+; (pixel[\daxl ]{0,6})(?: bui|\))/i // Google Pixel 833 | ], [MODEL, [VENDOR, GOOGLE], [TYPE, MOBILE]], [ 834 | 835 | // Sony 836 | /droid.+ (a?\d[0-2]{2}so|[c-g]\d{4}|so[-gl]\w+|xq-a\w[4-7][12])(?= bui|\).+chrome\/(?![1-6]{0,1}\d\.))/i 837 | ], [MODEL, [VENDOR, SONY], [TYPE, MOBILE]], [ 838 | /sony tablet [ps]/i, 839 | /\b(?:sony)?sgp\w+(?: bui|\))/i 840 | ], [[MODEL, 'Xperia Tablet'], [VENDOR, SONY], [TYPE, TABLET]], [ 841 | 842 | // OnePlus 843 | / (kb2005|in20[12]5|be20[12][59])\b/i, 844 | /(?:one)?(?:plus)? (a\d0\d\d)(?: b|\))/i 845 | ], [MODEL, [VENDOR, 'OnePlus'], [TYPE, MOBILE]], [ 846 | 847 | // Amazon 848 | /(alexa)webm/i, 849 | /(kf[a-z]{2}wi)( bui|\))/i, // Kindle Fire without Silk 850 | /(kf[a-z]+)( bui|\)).+silk\//i // Kindle Fire HD 851 | ], [MODEL, [VENDOR, AMAZON], [TYPE, TABLET]], [ 852 | /((?:sd|kf)[0349hijorstuw]+)( bui|\)).+silk\//i // Fire Phone 853 | ], [[MODEL, /(.+)/g, 'Fire Phone $1'], [VENDOR, AMAZON], [TYPE, MOBILE]], [ 854 | 855 | // BlackBerry 856 | /(playbook);[-\w\),; ]+(rim)/i // BlackBerry PlayBook 857 | ], [MODEL, VENDOR, [TYPE, TABLET]], [ 858 | /\b((?:bb[a-f]|st[hv])100-\d)/i, 859 | /\(bb10; (\w+)/i // BlackBerry 10 860 | ], [MODEL, [VENDOR, BLACKBERRY], [TYPE, MOBILE]], [ 861 | 862 | // Asus 863 | /(?:\b|asus_)(transfo[prime ]{4,10} \w+|eeepc|slider \w+|nexus 7|padfone|p00[cj])/i 864 | ], [MODEL, [VENDOR, ASUS], [TYPE, TABLET]], [ 865 | / (z[bes]6[027][012][km][ls]|zenfone \d\w?)\b/i 866 | ], [MODEL, [VENDOR, ASUS], [TYPE, MOBILE]], [ 867 | 868 | // HTC 869 | /(nexus 9)/i // HTC Nexus 9 870 | ], [MODEL, [VENDOR, 'HTC'], [TYPE, TABLET]], [ 871 | /(htc)[-;_ ]{1,2}([\w ]+(?=\)| bui)|\w+)/i, // HTC 872 | 873 | // ZTE 874 | /(zte)[- ]([\w ]+?)(?: bui|\/|\))/i, 875 | /(alcatel|geeksphone|nexian|panasonic|sony(?!-bra))[-_ ]?([-\w]*)/i // Alcatel/GeeksPhone/Nexian/Panasonic/Sony 876 | ], [VENDOR, [MODEL, /_/g, ' '], [TYPE, MOBILE]], [ 877 | 878 | // Acer 879 | /droid.+; ([ab][1-7]-?[0178a]\d\d?)/i 880 | ], [MODEL, [VENDOR, 'Acer'], [TYPE, TABLET]], [ 881 | 882 | // Meizu 883 | /droid.+; (m[1-5] note) bui/i, 884 | /\bmz-([-\w]{2,})/i 885 | ], [MODEL, [VENDOR, 'Meizu'], [TYPE, MOBILE]], [ 886 | 887 | // Sharp 888 | /\b(sh-?[altvz]?\d\d[a-ekm]?)/i 889 | ], [MODEL, [VENDOR, 'Sharp'], [TYPE, MOBILE]], [ 890 | 891 | // MIXED 892 | /(blackberry|benq|palm(?=\-)|sonyericsson|acer|asus|dell|meizu|motorola|polytron)[-_ ]?([-\w]*)/i, 893 | // BlackBerry/BenQ/Palm/Sony-Ericsson/Acer/Asus/Dell/Meizu/Motorola/Polytron 894 | /(hp) ([\w ]+\w)/i, // HP iPAQ 895 | /(asus)-?(\w+)/i, // Asus 896 | /(microsoft); (lumia[\w ]+)/i, // Microsoft Lumia 897 | /(lenovo)[-_ ]?([-\w]+)/i, // Lenovo 898 | /(jolla)/i, // Jolla 899 | /(oppo) ?([\w ]+) bui/i // OPPO 900 | ], [VENDOR, MODEL, [TYPE, MOBILE]], [ 901 | 902 | /(archos) (gamepad2?)/i, // Archos 903 | /(hp).+(touchpad(?!.+tablet)|tablet)/i, // HP TouchPad 904 | /(kindle)\/([\w\.]+)/i, // Kindle 905 | /(nook)[\w ]+build\/(\w+)/i, // Nook 906 | /(dell) (strea[kpr\d ]*[\dko])/i, // Dell Streak 907 | /(le[- ]+pan)[- ]+(\w{1,9}) bui/i, // Le Pan Tablets 908 | /(trinity)[- ]*(t\d{3}) bui/i, // Trinity Tablets 909 | /(gigaset)[- ]+(q\w{1,9}) bui/i, // Gigaset Tablets 910 | /(vodafone) ([\w ]+)(?:\)| bui)/i // Vodafone 911 | ], [VENDOR, MODEL, [TYPE, TABLET]], [ 912 | 913 | /(surface duo)/i // Surface Duo 914 | ], [MODEL, [VENDOR, MICROSOFT], [TYPE, TABLET]], [ 915 | /droid [\d\.]+; (fp\du?)(?: b|\))/i // Fairphone 916 | ], [MODEL, [VENDOR, 'Fairphone'], [TYPE, MOBILE]], [ 917 | /(u304aa)/i // AT&T 918 | ], [MODEL, [VENDOR, 'AT&T'], [TYPE, MOBILE]], [ 919 | /\bsie-(\w*)/i // Siemens 920 | ], [MODEL, [VENDOR, 'Siemens'], [TYPE, MOBILE]], [ 921 | /\b(rct\w+) b/i // RCA Tablets 922 | ], [MODEL, [VENDOR, 'RCA'], [TYPE, TABLET]], [ 923 | /\b(venue[\d ]{2,7}) b/i // Dell Venue Tablets 924 | ], [MODEL, [VENDOR, 'Dell'], [TYPE, TABLET]], [ 925 | /\b(q(?:mv|ta)\w+) b/i // Verizon Tablet 926 | ], [MODEL, [VENDOR, 'Verizon'], [TYPE, TABLET]], [ 927 | /\b(?:barnes[& ]+noble |bn[rt])([\w\+ ]*) b/i // Barnes & Noble Tablet 928 | ], [MODEL, [VENDOR, 'Barnes & Noble'], [TYPE, TABLET]], [ 929 | /\b(tm\d{3}\w+) b/i 930 | ], [MODEL, [VENDOR, 'NuVision'], [TYPE, TABLET]], [ 931 | /\b(k88) b/i // ZTE K Series Tablet 932 | ], [MODEL, [VENDOR, 'ZTE'], [TYPE, TABLET]], [ 933 | /\b(nx\d{3}j) b/i // ZTE Nubia 934 | ], [MODEL, [VENDOR, 'ZTE'], [TYPE, MOBILE]], [ 935 | /\b(gen\d{3}) b.+49h/i // Swiss GEN Mobile 936 | ], [MODEL, [VENDOR, 'Swiss'], [TYPE, MOBILE]], [ 937 | /\b(zur\d{3}) b/i // Swiss ZUR Tablet 938 | ], [MODEL, [VENDOR, 'Swiss'], [TYPE, TABLET]], [ 939 | /\b((zeki)?tb.*\b) b/i // Zeki Tablets 940 | ], [MODEL, [VENDOR, 'Zeki'], [TYPE, TABLET]], [ 941 | /\b([yr]\d{2}) b/i, 942 | /\b(dragon[- ]+touch |dt)(\w{5}) b/i // Dragon Touch Tablet 943 | ], [[VENDOR, 'Dragon Touch'], MODEL, [TYPE, TABLET]], [ 944 | /\b(ns-?\w{0,9}) b/i // Insignia Tablets 945 | ], [MODEL, [VENDOR, 'Insignia'], [TYPE, TABLET]], [ 946 | /\b((nxa|next)-?\w{0,9}) b/i // NextBook Tablets 947 | ], [MODEL, [VENDOR, 'NextBook'], [TYPE, TABLET]], [ 948 | /\b(xtreme\_)?(v(1[045]|2[015]|[3469]0|7[05])) b/i // Voice Xtreme Phones 949 | ], [[VENDOR, 'Voice'], MODEL, [TYPE, MOBILE]], [ 950 | /\b(lvtel\-)?(v1[12]) b/i // LvTel Phones 951 | ], [[VENDOR, 'LvTel'], MODEL, [TYPE, MOBILE]], [ 952 | /\b(ph-1) /i // Essential PH-1 953 | ], [MODEL, [VENDOR, 'Essential'], [TYPE, MOBILE]], [ 954 | /\b(v(100md|700na|7011|917g).*\b) b/i // Envizen Tablets 955 | ], [MODEL, [VENDOR, 'Envizen'], [TYPE, TABLET]], [ 956 | /\b(trio[-\w\. ]+) b/i // MachSpeed Tablets 957 | ], [MODEL, [VENDOR, 'MachSpeed'], [TYPE, TABLET]], [ 958 | /\btu_(1491) b/i // Rotor Tablets 959 | ], [MODEL, [VENDOR, 'Rotor'], [TYPE, TABLET]], [ 960 | /(shield[\w ]+) b/i // Nvidia Shield Tablets 961 | ], [MODEL, [VENDOR, 'Nvidia'], [TYPE, TABLET]], [ 962 | /(sprint) (\w+)/i // Sprint Phones 963 | ], [VENDOR, MODEL, [TYPE, MOBILE]], [ 964 | /(kin\.[onetw]{3})/i // Microsoft Kin 965 | ], [[MODEL, /\./g, ' '], [VENDOR, MICROSOFT], [TYPE, MOBILE]], [ 966 | /droid.+; (cc6666?|et5[16]|mc[239][23]x?|vc8[03]x?)\)/i // Zebra 967 | ], [MODEL, [VENDOR, ZEBRA], [TYPE, TABLET]], [ 968 | /droid.+; (ec30|ps20|tc[2-8]\d[kx])\)/i 969 | ], [MODEL, [VENDOR, ZEBRA], [TYPE, MOBILE]], [ 970 | 971 | /////////////////// 972 | // CONSOLES 973 | /////////////////// 974 | 975 | /(ouya)/i, // Ouya 976 | /(nintendo) ([wids3utch]+)/i // Nintendo 977 | ], [VENDOR, MODEL, [TYPE, CONSOLE]], [ 978 | /droid.+; (shield) bui/i // Nvidia 979 | ], [MODEL, [VENDOR, 'Nvidia'], [TYPE, CONSOLE]], [ 980 | /(playstation [345portablevi]+)/i // Playstation 981 | ], [MODEL, [VENDOR, SONY], [TYPE, CONSOLE]], [ 982 | /\b(xbox(?: one)?(?!; xbox))[\); ]/i // Microsoft Xbox 983 | ], [MODEL, [VENDOR, MICROSOFT], [TYPE, CONSOLE]], [ 984 | 985 | /////////////////// 986 | // SMARTTVS 987 | /////////////////// 988 | 989 | /smart-tv.+(samsung)/i // Samsung 990 | ], [VENDOR, [TYPE, SMARTTV]], [ 991 | /hbbtv.+maple;(\d+)/i 992 | ], [[MODEL, /^/, 'SmartTV'], [VENDOR, SAMSUNG], [TYPE, SMARTTV]], [ 993 | /(nux; netcast.+smarttv|lg (netcast\.tv-201\d|android tv))/i // LG SmartTV 994 | ], [[VENDOR, LG], [TYPE, SMARTTV]], [ 995 | /(apple) ?tv/i // Apple TV 996 | ], [VENDOR, [MODEL, APPLE + ' TV'], [TYPE, SMARTTV]], [ 997 | /crkey/i // Google Chromecast 998 | ], [[MODEL, CHROME + 'cast'], [VENDOR, GOOGLE], [TYPE, SMARTTV]], [ 999 | /droid.+aft(\w)( bui|\))/i // Fire TV 1000 | ], [MODEL, [VENDOR, AMAZON], [TYPE, SMARTTV]], [ 1001 | /\(dtv[\);].+(aquos)/i // Sharp 1002 | ], [MODEL, [VENDOR, 'Sharp'], [TYPE, SMARTTV]], [ 1003 | /(bravia[\w- ]+) bui/i // Sony 1004 | ], [MODEL, [VENDOR, SONY], [TYPE, SMARTTV]], [ 1005 | /\b(roku)[\dx]*[\)\/]((?:dvp-)?[\d\.]*)/i, // Roku 1006 | /hbbtv\/\d+\.\d+\.\d+ +\([\w ]*; *(\w[^;]*);([^;]*)/i // HbbTV devices 1007 | ], [[VENDOR, trim], [MODEL, trim], [TYPE, SMARTTV]], [ 1008 | /\b(android tv|smart[- ]?tv|opera tv|tv; rv:)\b/i // SmartTV from Unidentified Vendors 1009 | ], [[TYPE, SMARTTV]], [ 1010 | 1011 | /////////////////// 1012 | // WEARABLES 1013 | /////////////////// 1014 | 1015 | /((pebble))app/i // Pebble 1016 | ], [VENDOR, MODEL, [TYPE, WEARABLE]], [ 1017 | /droid.+; (glass) \d/i // Google Glass 1018 | ], [MODEL, [VENDOR, GOOGLE], [TYPE, WEARABLE]], [ 1019 | /droid.+; (wt63?0{2,3})\)/i 1020 | ], [MODEL, [VENDOR, ZEBRA], [TYPE, WEARABLE]], [ 1021 | /(quest( 2)?)/i // Oculus Quest 1022 | ], [MODEL, [VENDOR, FACEBOOK], [TYPE, WEARABLE]], [ 1023 | 1024 | /////////////////// 1025 | // EMBEDDED 1026 | /////////////////// 1027 | 1028 | /(tesla)(?: qtcarbrowser|\/[-\w\.]+)/i // Tesla 1029 | ], [VENDOR, [TYPE, EMBEDDED]], [ 1030 | 1031 | //////////////////// 1032 | // MIXED (GENERIC) 1033 | /////////////////// 1034 | 1035 | /droid .+?; ([^;]+?)(?: bui|\) applew).+? mobile safari/i // Android Phones from Unidentified Vendors 1036 | ], [MODEL, [TYPE, MOBILE]], [ 1037 | /droid .+?; ([^;]+?)(?: bui|\) applew).+?(?! mobile) safari/i // Android Tablets from Unidentified Vendors 1038 | ], [MODEL, [TYPE, TABLET]], [ 1039 | /\b((tablet|tab)[;\/]|focus\/\d(?!.+mobile))/i // Unidentifiable Tablet 1040 | ], [[TYPE, TABLET]], [ 1041 | /(phone|mobile(?:[;\/]| safari)|pda(?=.+windows ce))/i // Unidentifiable Mobile 1042 | ], [[TYPE, MOBILE]], [ 1043 | /(android[-\w\. ]{0,9});.+buil/i // Generic Android Device 1044 | ], [MODEL, [VENDOR, 'Generic']] 1045 | ], 1046 | 1047 | engine: [[ 1048 | 1049 | /windows.+ edge\/([\w\.]+)/i // EdgeHTML 1050 | ], [VERSION, [NAME, EDGE + 'HTML']], [ 1051 | 1052 | /webkit\/537\.36.+chrome\/(?!27)([\w\.]+)/i // Blink 1053 | ], [VERSION, [NAME, 'Blink']], [ 1054 | 1055 | /(presto)\/([\w\.]+)/i, // Presto 1056 | /(webkit|trident|netfront|netsurf|amaya|lynx|w3m|goanna)\/([\w\.]+)/i, // WebKit/Trident/NetFront/NetSurf/Amaya/Lynx/w3m/Goanna 1057 | /ekioh(flow)\/([\w\.]+)/i, // Flow 1058 | /(khtml|tasman|links)[\/ ]\(?([\w\.]+)/i, // KHTML/Tasman/Links 1059 | /(icab)[\/ ]([23]\.[\d\.]+)/i // iCab 1060 | ], [NAME, VERSION], [ 1061 | 1062 | /rv\:([\w\.]{1,9})\b.+(gecko)/i // Gecko 1063 | ], [VERSION, NAME] 1064 | ], 1065 | 1066 | os: [[ 1067 | 1068 | // Windows 1069 | /microsoft (windows) (vista|xp)/i // Windows (iTunes) 1070 | ], [NAME, VERSION], [ 1071 | /(windows) nt 6\.2; (arm)/i, // Windows RT 1072 | /(windows (?:phone(?: os)?|mobile))[\/ ]?([\d\.\w ]*)/i, // Windows Phone 1073 | /(windows)[\/ ]?([ntce\d\. ]+\w)(?!.+xbox)/i 1074 | ], [NAME, [VERSION, strMapper, windowsVersionMap]], [ 1075 | /(win(?=3|9|n)|win 9x )([nt\d\.]+)/i 1076 | ], [[NAME, 'Windows'], [VERSION, strMapper, windowsVersionMap]], [ 1077 | 1078 | // iOS/macOS 1079 | /ip[honead]{2,4}\b(?:.*os ([\w]+) like mac|; opera)/i, // iOS 1080 | /cfnetwork\/.+darwin/i 1081 | ], [[VERSION, /_/g, '.'], [NAME, 'iOS']], [ 1082 | /(mac os x) ?([\w\. ]*)/i, 1083 | /(macintosh|mac_powerpc\b)(?!.+haiku)/i // Mac OS 1084 | ], [[NAME, 'Mac OS'], [VERSION, /_/g, '.']], [ 1085 | 1086 | // Mobile OSes 1087 | /droid ([\w\.]+)\b.+(android[- ]x86)/i // Android-x86 1088 | ], [VERSION, NAME], [ // Android/WebOS/QNX/Bada/RIM/Maemo/MeeGo/Sailfish OS 1089 | /(android|webos|qnx|bada|rim tablet os|maemo|meego|sailfish)[-\/ ]?([\w\.]*)/i, 1090 | /(blackberry)\w*\/([\w\.]*)/i, // Blackberry 1091 | /(tizen|kaios)[\/ ]([\w\.]+)/i, // Tizen/KaiOS 1092 | /\((series40);/i // Series 40 1093 | ], [NAME, VERSION], [ 1094 | /\(bb(10);/i // BlackBerry 10 1095 | ], [VERSION, [NAME, BLACKBERRY]], [ 1096 | /(?:symbian ?os|symbos|s60(?=;)|series60)[-\/ ]?([\w\.]*)/i // Symbian 1097 | ], [VERSION, [NAME, 'Symbian']], [ 1098 | /mozilla\/[\d\.]+ \((?:mobile|tablet|tv|mobile; [\w ]+); rv:.+ gecko\/([\w\.]+)/i // Firefox OS 1099 | ], [VERSION, [NAME, FIREFOX + ' OS']], [ 1100 | /web0s;.+rt(tv)/i, 1101 | /\b(?:hp)?wos(?:browser)?\/([\w\.]+)/i // WebOS 1102 | ], [VERSION, [NAME, 'webOS']], [ 1103 | 1104 | // Google Chromecast 1105 | /crkey\/([\d\.]+)/i // Google Chromecast 1106 | ], [VERSION, [NAME, CHROME + 'cast']], [ 1107 | /(cros) [\w]+ ([\w\.]+\w)/i // Chromium OS 1108 | ], [[NAME, 'Chromium OS'], VERSION], [ 1109 | 1110 | // Console 1111 | /(nintendo|playstation) ([wids345portablevuch]+)/i, // Nintendo/Playstation 1112 | /(xbox); +xbox ([^\);]+)/i, // Microsoft Xbox (360, One, X, S, Series X, Series S) 1113 | 1114 | // Other 1115 | /\b(joli|palm)\b ?(?:os)?\/?([\w\.]*)/i, // Joli/Palm 1116 | /(mint)[\/\(\) ]?(\w*)/i, // Mint 1117 | /(mageia|vectorlinux)[; ]/i, // Mageia/VectorLinux 1118 | /([kxln]?ubuntu|debian|suse|opensuse|gentoo|arch(?= linux)|slackware|fedora|mandriva|centos|pclinuxos|red ?hat|zenwalk|linpus|raspbian|plan 9|minix|risc os|contiki|deepin|manjaro|elementary os|sabayon|linspire)(?: gnu\/linux)?(?: enterprise)?(?:[- ]linux)?(?:-gnu)?[-\/ ]?(?!chrom|package)([-\w\.]*)/i, 1119 | // Ubuntu/Debian/SUSE/Gentoo/Arch/Slackware/Fedora/Mandriva/CentOS/PCLinuxOS/RedHat/Zenwalk/Linpus/Raspbian/Plan9/Minix/RISCOS/Contiki/Deepin/Manjaro/elementary/Sabayon/Linspire 1120 | /(hurd|linux) ?([\w\.]*)/i, // Hurd/Linux 1121 | /(gnu) ?([\w\.]*)/i, // GNU 1122 | /\b([-frentopcghs]{0,5}bsd|dragonfly)[\/ ]?(?!amd|[ix346]{1,2}86)([\w\.]*)/i, // FreeBSD/NetBSD/OpenBSD/PC-BSD/GhostBSD/DragonFly 1123 | /(haiku) (\w+)/i // Haiku 1124 | ], [NAME, VERSION], [ 1125 | /(sunos) ?([\w\.\d]*)/i // Solaris 1126 | ], [[NAME, 'Solaris'], VERSION], [ 1127 | /((?:open)?solaris)[-\/ ]?([\w\.]*)/i, // Solaris 1128 | /(aix) ((\d)(?=\.|\)| )[\w\.])*/i, // AIX 1129 | /\b(beos|os\/2|amigaos|morphos|openvms|fuchsia|hp-ux)/i, // BeOS/OS2/AmigaOS/MorphOS/OpenVMS/Fuchsia/HP-UX 1130 | /(unix) ?([\w\.]*)/i // UNIX 1131 | ], [NAME, VERSION] 1132 | ] 1133 | }; 1134 | 1135 | ///////////////// 1136 | // Constructor 1137 | //////////////// 1138 | 1139 | var UAParser = function (ua, extensions) { 1140 | 1141 | if (typeof ua === OBJ_TYPE) { 1142 | extensions = ua; 1143 | ua = undefined; 1144 | } 1145 | 1146 | if (!(this instanceof UAParser)) { 1147 | return new UAParser(ua, extensions).getResult(); 1148 | } 1149 | 1150 | var _ua = ua || ((typeof window !== UNDEF_TYPE && window.navigator && window.navigator.userAgent) ? window.navigator.userAgent : EMPTY); 1151 | var _rgxmap = extensions ? extend(regexes, extensions) : regexes; 1152 | 1153 | this.getBrowser = function () { 1154 | var _browser = {}; 1155 | _browser[NAME] = undefined; 1156 | _browser[VERSION] = undefined; 1157 | rgxMapper.call(_browser, _ua, _rgxmap.browser); 1158 | _browser.major = majorize(_browser.version); 1159 | return _browser; 1160 | }; 1161 | this.getCPU = function () { 1162 | var _cpu = {}; 1163 | _cpu[ARCHITECTURE] = undefined; 1164 | rgxMapper.call(_cpu, _ua, _rgxmap.cpu); 1165 | return _cpu; 1166 | }; 1167 | this.getDevice = function () { 1168 | var _device = {}; 1169 | _device[VENDOR] = undefined; 1170 | _device[MODEL] = undefined; 1171 | _device[TYPE] = undefined; 1172 | rgxMapper.call(_device, _ua, _rgxmap.device); 1173 | return _device; 1174 | }; 1175 | this.getEngine = function () { 1176 | var _engine = {}; 1177 | _engine[NAME] = undefined; 1178 | _engine[VERSION] = undefined; 1179 | rgxMapper.call(_engine, _ua, _rgxmap.engine); 1180 | return _engine; 1181 | }; 1182 | this.getOS = function () { 1183 | var _os = {}; 1184 | _os[NAME] = undefined; 1185 | _os[VERSION] = undefined; 1186 | rgxMapper.call(_os, _ua, _rgxmap.os); 1187 | return _os; 1188 | }; 1189 | this.getResult = function () { 1190 | return { 1191 | ua: this.getUA(), 1192 | browser: this.getBrowser(), 1193 | engine: this.getEngine(), 1194 | os: this.getOS(), 1195 | device: this.getDevice(), 1196 | cpu: this.getCPU() 1197 | }; 1198 | }; 1199 | this.getUA = function () { 1200 | return _ua; 1201 | }; 1202 | this.setUA = function (ua) { 1203 | _ua = (typeof ua === STR_TYPE && ua.length > UA_MAX_LENGTH) ? trim(ua, UA_MAX_LENGTH) : ua; 1204 | return this; 1205 | }; 1206 | this.setUA(_ua); 1207 | return this; 1208 | }; 1209 | 1210 | UAParser.VERSION = LIBVERSION; 1211 | UAParser.BROWSER = enumerize([NAME, VERSION, MAJOR]); 1212 | UAParser.CPU = enumerize([ARCHITECTURE]); 1213 | UAParser.DEVICE = enumerize([MODEL, VENDOR, TYPE, CONSOLE, MOBILE, SMARTTV, TABLET, WEARABLE, EMBEDDED]); 1214 | UAParser.ENGINE = UAParser.OS = enumerize([NAME, VERSION]); 1215 | 1216 | /////////// 1217 | // Export 1218 | ////////// 1219 | 1220 | // check js environment 1221 | if (typeof (exports) !== UNDEF_TYPE) { 1222 | // nodejs env 1223 | if (typeof module !== UNDEF_TYPE && module.exports) { 1224 | exports = module.exports = UAParser; 1225 | } 1226 | exports.UAParser = UAParser; 1227 | } else { 1228 | // requirejs env (optional) 1229 | if (typeof (define) === FUNC_TYPE && define.amd) { 1230 | define(function () { 1231 | return UAParser; 1232 | }); 1233 | } else if (typeof window !== UNDEF_TYPE) { 1234 | // browser env 1235 | window.UAParser = UAParser; 1236 | } 1237 | } 1238 | 1239 | // jQuery/Zepto specific (optional) 1240 | // Note: 1241 | // In AMD env the global scope should be kept clean, but jQuery is an exception. 1242 | // jQuery always exports to global scope, unless jQuery.noConflict(true) is used, 1243 | // and we should catch that. 1244 | var $ = typeof window !== UNDEF_TYPE && (window.jQuery || window.Zepto); 1245 | if ($ && !$.ua) { 1246 | var parser = new UAParser(); 1247 | $.ua = parser.getResult(); 1248 | $.ua.get = function () { 1249 | return parser.getUA(); 1250 | }; 1251 | $.ua.set = function (ua) { 1252 | parser.setUA(ua); 1253 | var result = parser.getResult(); 1254 | for (var prop in result) { 1255 | $.ua[prop] = result[prop]; 1256 | } 1257 | }; 1258 | } 1259 | 1260 | })(typeof window === 'object' ? window : this); --------------------------------------------------------------------------------