diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d97e430 --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +# Files and directories created by pub +.packages +.pub/ +build/ +coverage/ +packages +pubspec.lock + +# Directory created by dartdoc +doc/api/ + +# JetBrains IDEs +.idea/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..9493cb6 --- /dev/null +++ b/LICENSE @@ -0,0 +1,13 @@ +Copyright 2015 Workiva Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. \ No newline at end of file diff --git a/README.md b/README.md index eb70c30..74b71d1 100644 --- a/README.md +++ b/README.md @@ -1 +1,21 @@ # platform_detect + +A library for detecting browser and platform type and version. + +## Usage + +A simple usage example: + +```dart +import 'package:platform_detect/platform_detect.dart'; + +main() { + if (browser.isChrome) { + print('thank you for being a friend'); + } + + if (operatingSystem.isMac) { + print(''); + } +} +``` diff --git a/example/index.html b/example/index.html new file mode 100644 index 0000000..1fab873 --- /dev/null +++ b/example/index.html @@ -0,0 +1,58 @@ + + + + Platform Detect example + + + +
+

Current platform

+

Your Browser:

+ + +
+ Version: +
+

Your Operating System:

+ +

Test other values

+ + + + + + + + + + + + + + + + + +
vendor
appVersion
appName
userAgent
+ +

Evaluates to:

+ +
+ + + + + diff --git a/example/main.dart b/example/main.dart new file mode 100644 index 0000000..8712337 --- /dev/null +++ b/example/main.dart @@ -0,0 +1,53 @@ +import 'dart:html'; +import 'package:platform_detect/src/navigator.dart'; +import 'package:platform_detect/src/browser.dart'; +import 'package:platform_detect/src/operating_system.dart'; +import 'package:platform_detect/platform_detect.dart'; + +main() { + _parseCurrentBrowser(); + ButtonElement evaluate = querySelector('#evaluate-test'); + evaluate.onClick.listen((_) => _parseTestValues()); +} + +void _parseCurrentBrowser() { + document.querySelector('#current-browser').text = browser.name; + document.querySelector('#current-vendor').text = window.navigator.vendor; + document.querySelector('#current-appVersion').text = window.navigator.appVersion; + document.querySelector('#current-appName').text = window.navigator.appName; + document.querySelector('#current-userAgent').text = window.navigator.userAgent; + + CheckboxInputElement isChrome = document.querySelector('#current-is-chrome'); + isChrome.checked = browser.isChrome; + + CheckboxInputElement isFirefox = document.querySelector('#current-is-firefox'); + isFirefox.checked = browser.isFirefox; + + CheckboxInputElement isSafari = document.querySelector('#current-is-safari'); + isSafari.checked = browser.isSafari; + + CheckboxInputElement isInternetExplorer = document.querySelector('#current-is-ie'); + isInternetExplorer.checked = browser.isInternetExplorer; + + document.querySelector('#current-version').text = browser.version.toString(); + + document.querySelector('#current-os').text = operatingSystem.name; +} + +void _parseTestValues() { + InputElement testVendor = querySelector('#test-vendor'); + InputElement testAppVersion = querySelector('#test-appVersion'); + InputElement testAppName = querySelector('#test-appName'); + InputElement testUserAgent = querySelector('#test-userAgent'); + var navigator = new TestNavigator(); + navigator.vendor = testVendor.value.trim(); + navigator.appVersion = testAppVersion.value.trim(); + navigator.appName = testAppName.value.trim(); + navigator.userAgent = testUserAgent.value.trim(); + Browser.navigator = navigator; + OperatingSystem.navigator = navigator; + + querySelector('#test-browser-name').text = browser.name; + querySelector('#test-browser-version').text = browser.version.toString(); + querySelector('#test-os-name').text = operatingSystem.name; +} \ No newline at end of file diff --git a/lib/platform_detect.dart b/lib/platform_detect.dart new file mode 100644 index 0000000..f600a60 --- /dev/null +++ b/lib/platform_detect.dart @@ -0,0 +1,41 @@ +/// Enables detection of browser type and version and operating system +/// +/// Use browser.isChrome or operatingSystem.isMac +library platform_detect; + +import 'dart:html'; + +import 'package:platform_detect/src/browser.dart'; +import 'package:platform_detect/src/navigator.dart'; +import 'package:platform_detect/src/operating_system.dart'; + +Browser _browser; + +/// Current browser info +Browser get browser { + if (_browser == null) { + Browser.navigator = new _HtmlNavigator(); + _browser = Browser.getCurrentBrowser(); + } + + return _browser; +} + +OperatingSystem _operatingSystem; + +/// Current operating system info +OperatingSystem get operatingSystem { + if (_operatingSystem == null) { + OperatingSystem.navigator = new _HtmlNavigator(); + _operatingSystem = OperatingSystem.getCurrentOperatingSystem(); + } + + return _operatingSystem; +} + +class _HtmlNavigator implements NavigatorProvider { + String get vendor => window.navigator.vendor; + String get appVersion => window.navigator.appVersion; + String get appName => window.navigator.appName; + String get userAgent => window.navigator.userAgent; +} diff --git a/lib/src/browser.dart b/lib/src/browser.dart new file mode 100644 index 0000000..1cf7304 --- /dev/null +++ b/lib/src/browser.dart @@ -0,0 +1,146 @@ +library platform_detect.browser; + +import 'package:pub_semver/pub_semver.dart'; +import 'package:platform_detect/src/navigator.dart'; + +/// Matches a browser name with how it is represented in window.navigator +class Browser { + static NavigatorProvider navigator; + + static Browser getCurrentBrowser() { + return _knownBrowsers.firstWhere( + (browser) => browser._matchesNavigator(navigator), + orElse: () => UnknownBrowser); + } + + static Browser UnknownBrowser = new Browser('Unknown', null, null); + + Browser(this.name, bool matchesNavigator(NavigatorProvider navigator), + Version parseVersion(NavigatorProvider navigator)) + : this._matchesNavigator = matchesNavigator, + this._parseVersion = parseVersion; + + final String name; + final Function _matchesNavigator; + final Function _parseVersion; + + Version _version; + + Version get version { + if (_version == null) { + if (_parseVersion != null) { + _version = _parseVersion(Browser.navigator); + } else { + _version = new Version(0, 0, 0); + } + } + + return _version; + } + + static List _knownBrowsers = [ + _chrome, + _firefox, + _safari, + _internetExplorer + ]; + + bool get isChrome => this == _chrome; + bool get isFirefox => this == _firefox; + bool get isSafari => this == _safari; + bool get isInternetExplorer => this == _internetExplorer; +} + +Browser _chrome = new _Chrome(); +Browser _firefox = new _Firefox(); +Browser _safari = new _Safari(); +Browser _internetExplorer = new _InternetExplorer(); + +class _Chrome extends Browser { + _Chrome() : super('Chrome', _isChrome, _getVersion); + + static bool _isChrome(NavigatorProvider navigator) { + var vendor = navigator.vendor; + return vendor != null && vendor.contains('Google'); + } + + static Version _getVersion(NavigatorProvider navigator) { + Match match = new RegExp(r"Chrome/(\d+)\.(\d+)\.(\d+)\.(\d+)\s") + .firstMatch(navigator.appVersion); + var major = int.parse(match.group(1)); + var minor = int.parse(match.group(2)); + var patch = int.parse(match.group(3)); + var build = match.group(4); + return new Version(major, minor, patch, build: build); + } +} + +class _Firefox extends Browser { + _Firefox() : super('Firefox', _isFirefox, _getVersion); + + static bool _isFirefox(NavigatorProvider navigator) { + return navigator.userAgent.contains('Firefox'); + } + + static Version _getVersion(NavigatorProvider navigator) { + Match match = + new RegExp(r'rv:(\d+)\.(\d+)\)').firstMatch(navigator.userAgent); + var major = int.parse(match.group(1)); + var minor = int.parse(match.group(2)); + return new Version(major, minor, 0); + } +} + +class _Safari extends Browser { + _Safari() : super('Safari', _isSafari, _getVersion); + + static bool _isSafari(NavigatorProvider navigator) { + return navigator.vendor.contains('Apple'); + } + + static Version _getVersion(NavigatorProvider navigator) { + Match match = new RegExp(r'Version/(\d+)\.(\d+)\.(\d+)') + .firstMatch(navigator.appVersion); + var major = int.parse(match.group(1)); + var minor = int.parse(match.group(2)); + var patch = int.parse(match.group(3)); + return new Version(major, minor, patch); + } +} + +class _InternetExplorer extends Browser { + _InternetExplorer() + : super('Internet Explorer', _isInternetExplorer, _getVersion); + + static bool _isInternetExplorer(NavigatorProvider navigator) { + return navigator.appName.contains('Microsoft') || + navigator.appVersion.contains('Trident') || + navigator.appVersion.contains('Edge'); + } + + static Version _getVersion(NavigatorProvider navigator) { + Match match = + new RegExp(r'MSIE (\d+)\.(\d+);').firstMatch(navigator.appVersion); + if (match != null) { + var major = int.parse(match.group(1)); + var minor = int.parse(match.group(2)); + return new Version(major, minor, 0); + } + + match = new RegExp(r'rv[: ](\d+)\.(\d+)').firstMatch(navigator.appVersion); + if (match != null) { + var major = int.parse(match.group(1)); + var minor = int.parse(match.group(2)); + return new Version(major, minor, 0); + } + + match = new RegExp(r'Edge/(\d+)\.(\d+)$').firstMatch(navigator.appVersion); + if (match != null) { + var major = int.parse(match.group(1)); + var minor = int.parse(match.group(2)); + return new Version(major, minor, 0); + } + + return new Version(0, 0, 0); + } +} diff --git a/lib/src/navigator.dart b/lib/src/navigator.dart new file mode 100644 index 0000000..efd192d --- /dev/null +++ b/lib/src/navigator.dart @@ -0,0 +1,17 @@ +library platform_detect.navigator; + +/// Abstraction over window.navigator so we can run tests in the VM +abstract class NavigatorProvider { + String get vendor; + String get appVersion; + String get appName; + String get userAgent; +} + +/// Simple implementation that enables ease of unit testing +class TestNavigator implements NavigatorProvider { + String vendor = ''; + String appVersion = ''; + String appName = ''; + String userAgent = ''; +} diff --git a/lib/src/operating_system.dart b/lib/src/operating_system.dart new file mode 100644 index 0000000..ba14f04 --- /dev/null +++ b/lib/src/operating_system.dart @@ -0,0 +1,49 @@ +library platform_detect.operating_system; + +import 'package:platform_detect/src/navigator.dart'; + +/// Matches an operating system name with how it is represented in window.navigator +class OperatingSystem { + static NavigatorProvider navigator; + + static OperatingSystem getCurrentOperatingSystem() { + return _knownSystems.firstWhere( + (system) => system._matchesNavigator(navigator), + orElse: () => UnknownOS); + } + + static OperatingSystem UnknownOS = new OperatingSystem('Unknown', null); + + final String name; + final Function _matchesNavigator; + + OperatingSystem(this.name, bool matchesNavigator(NavigatorProvider navigator)) + : this._matchesNavigator = matchesNavigator; + + static List _knownSystems = [_mac, _windows, _unix, _linux]; + + get isMac => this == _mac; + get isWindows => this == _windows; + get isUnix => this == _unix; + get isLinux => this == _linux; + + static OperatingSystem _mac = + new OperatingSystem('Mac', (NavigatorProvider navigator) { + return navigator.appVersion.contains('Mac'); + }); + + static OperatingSystem _windows = + new OperatingSystem('Windows', (NavigatorProvider navigator) { + return navigator.appVersion.contains('Win'); + }); + + static OperatingSystem _unix = + new OperatingSystem('Unix', (NavigatorProvider navigator) { + return navigator.appVersion.contains('X11'); + }); + + static OperatingSystem _linux = + new OperatingSystem('Linux', (NavigatorProvider navigator) { + return navigator.appVersion.contains('Linux'); + }); +} diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 0000000..6c1cbbf --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,21 @@ +name: platform_detect +description: A lightweight library for detecting the running browser and OS +version: 0.0.1 +author: Workiva Client Platform Team +homepage: https://github.com/Workiva/platform_detect +documentation: https://docs.workiva.org/platform_detect/latest/ +publish_to: https://pub.workiva.org + +environment: + sdk: ">=1.12.1 <2.0.0" + +dependencies: + pub_semver: ^1.0.0 + +dev_dependencies: + dartdoc: ^0.9.7 + dart_style: 0.2.10 + coverage: ^0.7.9 + browser: any + dart_dev: ^1.4.2 + test: ^0.12.0 diff --git a/smithy.yml b/smithy.yml new file mode 100644 index 0000000..4e2d873 --- /dev/null +++ b/smithy.yml @@ -0,0 +1,24 @@ +project: dart +language: dart + +# dart 1.19.1, built from https://github.com/Workiva/smithy-runner-dart/tree/0.0.4 +runner_image: drydock-prod.workiva.org/workiva/smithy-runner-dart:74173 + +script: + - pub get + - pub run dart_dev format --check + - pub run dart_dev analyze + - pub run dart_dev test + +after_script: + - pub run dart_dev docs --no-open + - cd doc/api && tar -zcvf api.tar.gz * && cd ../.. + - tar czvf platform_detect.pub.tgz LICENSE README.md pubspec.yaml lib/ + +artifacts: + build: + - ./pubspec.lock + documentation: + - ./doc/api/api.tar.gz + pub: + - ./platform_detect.pub.tgz \ No newline at end of file diff --git a/test/browser_test.dart b/test/browser_test.dart new file mode 100644 index 0000000..bd1df2f --- /dev/null +++ b/test/browser_test.dart @@ -0,0 +1,100 @@ +@TestOn('vm') +import 'package:platform_detect/src/browser.dart'; +import 'package:platform_detect/src/navigator.dart'; +import 'package:pub_semver/pub_semver.dart'; +import 'package:test/test.dart'; + +void main() { + group('browser detects', () { + tearDown(() { + Browser.navigator = null; + }); + + test('Unknown Browser', () { + Browser.navigator = new TestNavigator(); + var browser = Browser.getCurrentBrowser(); + expect(browser.name, Browser.UnknownBrowser.name); + expect(browser.version, Browser.UnknownBrowser.version); + expect(browser.isChrome, false); + expect(browser.isFirefox, false); + expect(browser.isSafari, false); + expect(browser.isInternetExplorer, false); + }); + + test('Fake Browser', () { + Browser browser = new Browser( + 'Fake', (_) => true, (_) => new Version(1, 1, 0)); + expect(browser.name, 'Fake'); + expect(browser.version, new Version(1, 1, 0)); + expect(browser.isChrome, false); + expect(browser.isFirefox, false); + expect(browser.isSafari, false); + expect(browser.isInternetExplorer, false); + }); + + test('Chrome', () { + var navigator = new TestNavigator() + ..userAgent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36' + ..appVersion = '5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36' + ..vendor = 'Google Inc.'; + Browser.navigator = navigator; + Browser browser = Browser.getCurrentBrowser(); + + expect(browser.name, 'Chrome'); + expect(browser.isChrome, true); + expect(browser.isFirefox, false); + expect(browser.isSafari, false); + expect(browser.isInternetExplorer, false); + expect(browser.version, new Version(53, 0, 2785, build: '143')); + }); + + test('Internet Explorer', () { + var navigator = new TestNavigator() + ..userAgent = 'Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E; rv:11.0) like Gecko' + ..appVersion = '5.0 (Windows NT 6.1; WOW64; Trident/7.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E; rv:11.0) like Gecko' + ..appName = 'Netscape'; + Browser.navigator = navigator; + Browser browser = Browser.getCurrentBrowser(); + + expect(browser.name, 'Internet Explorer'); + expect(browser.isChrome, false); + expect(browser.isFirefox, false); + expect(browser.isSafari, false); + expect(browser.isInternetExplorer, true); + expect(browser.version, new Version(11, 0, 0)); + }); + + test('Firefox', () { + var navigator = new TestNavigator() + ..userAgent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:48.0) Gecko/20100101 Firefox/48.0' + ..appVersion = '5.0 (Macintosh)' + ..appName = 'Netscape'; + Browser.navigator = navigator; + Browser browser = Browser.getCurrentBrowser(); + + expect(browser.name, 'Firefox'); + expect(browser.isChrome, false); + expect(browser.isFirefox, true); + expect(browser.isSafari, false); + expect(browser.isInternetExplorer, false); + expect(browser.version, new Version(48, 0, 0)); + }); + + test('Safari', () { + var navigator = new TestNavigator() + ..vendor = 'Apple Computer, Inc.' + ..userAgent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/601.7.8 (KHTML, like Gecko) Version/9.1.3 Safari/601.7.8' + ..appVersion = '5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/601.7.8 (KHTML, like Gecko) Version/9.1.3 Safari/601.7.8' + ..appName = 'Netscape'; + Browser.navigator = navigator; + Browser browser = Browser.getCurrentBrowser(); + + expect(browser.name, 'Safari'); + expect(browser.isChrome, false); + expect(browser.isFirefox, false); + expect(browser.isSafari, true); + expect(browser.isInternetExplorer, false); + expect(browser.version, new Version(9, 1, 3)); + }); + }); +} diff --git a/test/operating_system_test.dart b/test/operating_system_test.dart new file mode 100644 index 0000000..4e8e8a8 --- /dev/null +++ b/test/operating_system_test.dart @@ -0,0 +1,62 @@ +@TestOn('vm') +import 'package:platform_detect/src/operating_system.dart'; +import 'package:platform_detect/src/navigator.dart'; +import 'package:test/test.dart'; + +void main() { + group('operating system detects', () { + tearDown(() { + OperatingSystem.navigator = null; + }); + + test('Unknown Operating System', () { + OperatingSystem.navigator = new TestNavigator(); + var os = OperatingSystem.getCurrentOperatingSystem(); + expect(os.name, OperatingSystem.UnknownOS.name); + expect(os.isMac, false); + expect(os.isWindows, false); + expect(os.isUnix, false); + expect(os.isLinux, false); + }); + + test('Windows Operating System', () { + OperatingSystem.navigator = new TestNavigator()..appVersion = 'Windows'; + var os = OperatingSystem.getCurrentOperatingSystem(); + expect(os.name, 'Windows'); + expect(os.isMac, false); + expect(os.isWindows, true); + expect(os.isUnix, false); + expect(os.isLinux, false); + }); + + test('Mac Operating System', () { + OperatingSystem.navigator = new TestNavigator()..appVersion = 'Macintosh'; + var os = OperatingSystem.getCurrentOperatingSystem(); + expect(os.name, 'Mac'); + expect(os.isMac, true); + expect(os.isWindows, false); + expect(os.isUnix, false); + expect(os.isLinux, false); + }); + + test('Unix Operating System', () { + OperatingSystem.navigator = new TestNavigator()..appVersion = 'X11'; + var os = OperatingSystem.getCurrentOperatingSystem(); + expect(os.name, 'Unix'); + expect(os.isMac, false); + expect(os.isWindows, false); + expect(os.isUnix, true); + expect(os.isLinux, false); + }); + + test('Linux Operating System', () { + OperatingSystem.navigator = new TestNavigator()..appVersion = 'Linux'; + var os = OperatingSystem.getCurrentOperatingSystem(); + expect(os.name, 'Linux'); + expect(os.isMac, false); + expect(os.isWindows, false); + expect(os.isUnix, false); + expect(os.isLinux, true); + }); + }); +}