device_detector 1.0.0 → 1.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +58 -4
  3. data/README.md +57 -21
  4. data/lib/device_detector/bot.rb +2 -2
  5. data/lib/device_detector/browser.rb +691 -0
  6. data/lib/device_detector/client.rb +11 -2
  7. data/lib/device_detector/client_hint.rb +249 -0
  8. data/lib/device_detector/device.rb +1954 -23
  9. data/lib/device_detector/memory_cache.rb +26 -19
  10. data/lib/device_detector/metadata_extractor.rb +7 -8
  11. data/lib/device_detector/model_extractor.rb +3 -3
  12. data/lib/device_detector/name_extractor.rb +2 -2
  13. data/lib/device_detector/os.rb +289 -112
  14. data/lib/device_detector/parser.rb +49 -13
  15. data/lib/device_detector/vendor_fragment.rb +25 -0
  16. data/lib/device_detector/version.rb +3 -1
  17. data/lib/device_detector/version_extractor.rb +29 -2
  18. data/lib/device_detector.rb +192 -44
  19. data/regexes/bots.yml +3399 -91
  20. data/regexes/client/browser_engine.yml +28 -4
  21. data/regexes/client/browsers.yml +2697 -408
  22. data/regexes/client/feed_readers.yml +60 -22
  23. data/regexes/client/hints/apps.yml +150 -0
  24. data/regexes/client/hints/browsers.yml +292 -0
  25. data/regexes/client/libraries.yml +598 -4
  26. data/regexes/client/mediaplayers.yml +110 -5
  27. data/regexes/client/mobile_apps.yml +2451 -14
  28. data/regexes/client/pim.yml +128 -3
  29. data/regexes/device/cameras.yml +6 -6
  30. data/regexes/device/car_browsers.yml +39 -3
  31. data/regexes/device/consoles.yml +40 -6
  32. data/regexes/device/mobiles.yml +38844 -2907
  33. data/regexes/device/notebooks.yml +127 -0
  34. data/regexes/device/portable_media_player.yml +75 -12
  35. data/regexes/device/shell_tv.yml +145 -0
  36. data/regexes/device/televisions.yml +981 -40
  37. data/regexes/oss.yml +1560 -311
  38. data/regexes/vendorfragments.yml +6 -2
  39. metadata +31 -105
  40. data/.gitignore +0 -14
  41. data/.travis.yml +0 -18
  42. data/Gemfile +0 -8
  43. data/Rakefile +0 -79
  44. data/device_detector.gemspec +0 -26
  45. data/spec/device_detector/bot_fixtures_spec.rb +0 -30
  46. data/spec/device_detector/client_fixtures_spec.rb +0 -31
  47. data/spec/device_detector/concrete_user_agent_spec.rb +0 -136
  48. data/spec/device_detector/detector_fixtures_spec.rb +0 -60
  49. data/spec/device_detector/device_fixtures_spec.rb +0 -36
  50. data/spec/device_detector/device_spec.rb +0 -151
  51. data/spec/device_detector/memory_cache_spec.rb +0 -116
  52. data/spec/device_detector/model_extractor_spec.rb +0 -63
  53. data/spec/device_detector/os_fixtures_spec.rb +0 -26
  54. data/spec/device_detector/version_extractor_spec.rb +0 -80
  55. data/spec/device_detector_spec.rb +0 -198
  56. data/spec/fixtures/client/browser.yml +0 -1313
  57. data/spec/fixtures/client/feed_reader.yml +0 -187
  58. data/spec/fixtures/client/library.yml +0 -84
  59. data/spec/fixtures/client/mediaplayer.yml +0 -168
  60. data/spec/fixtures/client/mobile_app.yml +0 -30
  61. data/spec/fixtures/client/pim.yml +0 -96
  62. data/spec/fixtures/detector/bots.yml +0 -2418
  63. data/spec/fixtures/detector/camera.yml +0 -115
  64. data/spec/fixtures/detector/car_browser.yml +0 -20
  65. data/spec/fixtures/detector/console.yml +0 -267
  66. data/spec/fixtures/detector/desktop.yml +0 -4828
  67. data/spec/fixtures/detector/feature_phone.yml +0 -782
  68. data/spec/fixtures/detector/feed_reader.yml +0 -486
  69. data/spec/fixtures/detector/mediaplayer.yml +0 -179
  70. data/spec/fixtures/detector/mobile_apps.yml +0 -149
  71. data/spec/fixtures/detector/phablet.yml +0 -2140
  72. data/spec/fixtures/detector/portable_media_player.yml +0 -153
  73. data/spec/fixtures/detector/smart_display.yml +0 -58
  74. data/spec/fixtures/detector/smartphone-1.yml +0 -9469
  75. data/spec/fixtures/detector/smartphone-2.yml +0 -9414
  76. data/spec/fixtures/detector/smartphone-3.yml +0 -9396
  77. data/spec/fixtures/detector/smartphone-4.yml +0 -5742
  78. data/spec/fixtures/detector/smartphone.yml +0 -9411
  79. data/spec/fixtures/detector/tablet-1.yml +0 -9495
  80. data/spec/fixtures/detector/tablet-2.yml +0 -248
  81. data/spec/fixtures/detector/tablet.yml +0 -9484
  82. data/spec/fixtures/detector/tv.yml +0 -2582
  83. data/spec/fixtures/detector/unknown.yml +0 -3196
  84. data/spec/fixtures/device/camera.yml +0 -18
  85. data/spec/fixtures/device/car_browser.yml +0 -6
  86. data/spec/fixtures/device/console.yml +0 -78
  87. data/spec/fixtures/parser/oss.yml +0 -800
  88. data/spec/fixtures/parser/vendorfragments.yml +0 -162
  89. data/spec/spec_helper.rb +0 -9
@@ -1,7 +1,17 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class DeviceDetector
2
- class Parser < Struct.new(:user_agent)
4
+ class Parser
5
+ ROOT = File.expand_path('../..', __dir__)
6
+
7
+ REGEX_CACHE = ::DeviceDetector::MemoryCache.new({})
8
+ private_constant :REGEX_CACHE
9
+
10
+ def initialize(user_agent)
11
+ @user_agent = user_agent
12
+ end
3
13
 
4
- ROOT = File.expand_path('../../..', __FILE__)
14
+ attr_reader :user_agent
5
15
 
6
16
  def name
7
17
  from_cache(['name', self.class.name, user_agent]) do
@@ -32,51 +42,77 @@ class DeviceDetector
32
42
  end
33
43
 
34
44
  def filenames
35
- fail NotImplementedError
45
+ raise NotImplementedError
36
46
  end
37
47
 
38
48
  def filepaths
39
49
  filenames.map do |filename|
40
- [ filename.to_sym, File.join(ROOT, 'regexes', filename) ]
50
+ [filename.to_sym, File.join(ROOT, 'regexes', filename)]
41
51
  end
42
52
  end
43
53
 
44
54
  def regexes_for(file_paths)
45
- from_cache(['regexes', self.class]) do
55
+ REGEX_CACHE.get_or_set(file_paths) do
46
56
  load_regexes(file_paths).flat_map { |path, regex| parse_regexes(path, regex) }
47
57
  end
48
58
  end
49
59
 
50
60
  def load_regexes(file_paths)
51
- file_paths.map { |path, full_path| [path, symbolize_keys!(YAML.load_file(full_path))] }
61
+ file_paths.map do |path, full_path|
62
+ object = YAML.load_file(full_path)
63
+ object = rewrite_device_object!(object) if device_yml_file?(full_path)
64
+ object = rewrite_vendor_object!(object) if vendor_yml_file?(full_path)
65
+
66
+ [path, symbolize_keys!(object)]
67
+ end
68
+ end
69
+
70
+ def device_yml_file?(file_path)
71
+ file_path.include?('/regexes/device/')
72
+ end
73
+
74
+ def vendor_yml_file?(file_path)
75
+ file_path.include?('/regexes/vendorfragments')
76
+ end
77
+
78
+ def rewrite_vendor_object!(object)
79
+ object.map { |key, values| values.map { |v| { 'regex_name' => key, 'regex' => v } } }.flatten
80
+ end
81
+
82
+ def rewrite_device_object!(object)
83
+ object.map { |key, value| [key, { 'regex_name' => key }.merge!(value)] }.to_h
52
84
  end
53
85
 
54
86
  def symbolize_keys!(object)
55
87
  case object
56
88
  when Array
57
- object.map!{ |v| symbolize_keys!(v) }
89
+ object.map! { |v| symbolize_keys!(v) }
58
90
  when Hash
59
- object.keys.each{ |k| object[k.to_sym] = symbolize_keys!(object.delete(k)) if k.is_a?(String) }
91
+ keys = object.keys
92
+ keys.each do |k|
93
+ object[k.to_sym] = symbolize_keys!(object.delete(k)) if k.is_a?(String)
94
+ end
60
95
  end
61
96
  object
62
97
  end
63
98
 
64
99
  def parse_regexes(path, raw_regexes)
65
100
  raw_regexes.map do |meta|
66
- fail "invalid device spec: #{meta.inspect}" unless meta[:regex].is_a? String
101
+ raise "invalid device spec: #{meta.inspect}" unless meta[:regex].is_a? String
102
+
67
103
  meta[:regex] = build_regex(meta[:regex])
104
+ meta[:versions].each { |v| v[:regex] = build_regex(v[:regex]) } if meta.key?(:versions)
68
105
  meta[:path] = path
69
106
  meta
70
107
  end
71
108
  end
72
109
 
73
110
  def build_regex(src)
74
- Regexp.new('(?:^|[^A-Z0-9\-_]|[^A-Z0-9\-]_|sprd-)(?:' + src + ')', Regexp::IGNORECASE)
111
+ Regexp.new("(?:^|[^A-Z0-9_-]|[^A-Z0-9-]_|sprd-|MZ-)(?:#{src})", Regexp::IGNORECASE)
75
112
  end
76
113
 
77
- def from_cache(key)
78
- DeviceDetector.cache.get_or_set(key) { yield }
114
+ def from_cache(key, &block)
115
+ DeviceDetector.cache.get_or_set(key, &block)
79
116
  end
80
-
81
117
  end
82
118
  end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'set'
4
+
5
+ class DeviceDetector
6
+ class VendorFragment < Parser
7
+ def name
8
+ vendor_fragment_info
9
+ end
10
+
11
+ private
12
+
13
+ def vendor_fragment_info
14
+ from_cache(['vendor_fragment', self.class.name, user_agent]) do
15
+ return if regex_meta.nil? || regex_meta.empty?
16
+
17
+ regex_meta[:regex_name]
18
+ end
19
+ end
20
+
21
+ def filenames
22
+ ['vendorfragments.yml']
23
+ end
24
+ end
25
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class DeviceDetector
2
- VERSION = '1.0.0'
4
+ VERSION = '1.1.3'
3
5
  end
@@ -1,12 +1,39 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class DeviceDetector
2
4
  class VersionExtractor < MetadataExtractor
5
+ MAJOR_VERSION_2 = Gem::Version.new('2.0')
6
+ MAJOR_VERSION_3 = Gem::Version.new('3.0')
7
+ MAJOR_VERSION_4 = Gem::Version.new('4.0')
8
+ MAJOR_VERSION_8 = Gem::Version.new('8.0')
9
+
10
+ def call
11
+ simple_version = super&.chomp('.')
12
+
13
+ return simple_version unless simple_version&.empty?
14
+
15
+ os_version_by_regexes
16
+ end
3
17
 
4
18
  private
5
19
 
20
+ def os_version_by_regexes
21
+ version_matches = regex_meta[:versions]
22
+ return '' unless version_matches
23
+
24
+ version_matches.detect do |matcher|
25
+ user_agent.match(matcher[:regex]) do |match_data|
26
+ return matcher[:version].gsub(/\$(\d)/) do
27
+ match_data[Regexp.last_match(1).to_i].to_s
28
+ end.strip
29
+ end
30
+ end
31
+
32
+ ''
33
+ end
34
+
6
35
  def metadata_string
7
36
  String(regex_meta[:version])
8
37
  end
9
-
10
38
  end
11
39
  end
12
-
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'yaml'
2
4
 
3
5
  require 'device_detector/version'
@@ -11,50 +13,113 @@ require 'device_detector/bot'
11
13
  require 'device_detector/client'
12
14
  require 'device_detector/device'
13
15
  require 'device_detector/os'
16
+ require 'device_detector/browser'
17
+ require 'device_detector/client_hint'
18
+ require 'device_detector/vendor_fragment'
14
19
 
15
20
  class DeviceDetector
21
+ attr_reader :client_hint, :user_agent
22
+
23
+ def initialize(user_agent, headers = nil)
24
+ @client_hint = ClientHint.new(headers)
25
+ utf8_user_agent = encode_user_agent_if_needed(user_agent)
26
+ @user_agent = build_user_agent(utf8_user_agent)
27
+ end
28
+
29
+ # https://github.com/matomo-org/device-detector/blob/a2535ff3b63e4187f1d3440aed24ff43d74fb7f1/Parser/Device/AbstractDeviceParser.php#L2065-L2073
30
+ def build_user_agent(user_agent)
31
+ return user_agent if client_hint.model.nil?
32
+
33
+ regex = build_regex('Android 10[.\d]*; K(?: Build/|[;)])')
34
+ return user_agent unless user_agent =~ regex
35
+
36
+ version = client_hint.os_version || '10'
37
+
38
+ user_agent.gsub(/(Android 10[.\d]*; K)/, "Android #{version}; #{client_hint.model}")
39
+ end
16
40
 
17
- attr_reader :user_agent
41
+ def encode_user_agent_if_needed(user_agent)
42
+ return if user_agent.nil?
43
+ return user_agent if user_agent.encoding.name == 'UTF-8'
18
44
 
19
- def initialize(user_agent)
20
- @user_agent = user_agent
45
+ user_agent.encode('utf-8', 'binary', undef: :replace)
21
46
  end
22
47
 
23
48
  def name
24
- client.name
49
+ return client.name if mobile_fix?
50
+
51
+ client_hint.browser_name || client.name
25
52
  end
26
53
 
27
54
  def full_version
28
- client.full_version
55
+ client_hint.full_version || client.full_version
56
+ end
57
+
58
+ def os_family
59
+ return 'GNU/Linux' if linux_fix?
60
+
61
+ client_hint.os_family || os.family || client_hint.platform
29
62
  end
30
63
 
31
64
  def os_name
32
- os.name
65
+ return 'GNU/Linux' if linux_fix?
66
+
67
+ client_hint.os_name || os.name || client_hint.platform
33
68
  end
34
69
 
35
70
  def os_full_version
36
- os.full_version
71
+ return if skip_os_version?
72
+ return os.full_version if pico_os_fix?
73
+ return fire_os_version if fire_os_fix?
74
+
75
+ client_hint.os_version || os.full_version
37
76
  end
38
77
 
39
78
  def device_name
40
- device.name
79
+ return if fake_ua?
80
+
81
+ device.name || client_hint.model || fix_for_x_music
41
82
  end
42
83
 
43
84
  def device_brand
44
- device.brand
85
+ return if fake_ua?
86
+
87
+ # Assume all devices running iOS / Mac OS are from Apple
88
+ brand = device.brand
89
+ brand = 'Apple' if brand.nil? && DeviceDetector::OS::APPLE_OS_NAMES.include?(os_name)
90
+
91
+ brand
45
92
  end
46
93
 
47
94
  def device_type
48
95
  t = device.type
49
96
 
50
- if t.nil? && android_tablet_fragment? || opera_tablet?
51
- t = 'tablet'
52
- end
97
+ t = nil if fake_ua?
53
98
 
54
- if t.nil? && android_mobile_fragment?
55
- t = 'smartphone'
99
+ # Chrome on Android passes the device type based on the keyword 'Mobile'
100
+ # If it is present the device should be a smartphone, otherwise it's a tablet
101
+ # See https://developer.chrome.com/multidevice/user-agent#chrome_for_android_user_agent
102
+ # Note: We do not check for browser (family) here, as there might be mobile apps using Chrome,
103
+ # that won't have a detected browser, but can still be detected. So we check the useragent for
104
+ # Chrome instead.
105
+ if t.nil? && os_family == 'Android' && user_agent =~ build_regex('Chrome\/[\.0-9]*')
106
+ t = user_agent =~ build_regex('(?:Mobile|eliboM)') ? 'smartphone' : 'tablet'
56
107
  end
57
108
 
109
+ # Some UA contain the fragment 'Pad/APad', so we assume those devices as tablets
110
+ t = 'tablet' if t == 'smartphone' && user_agent =~ build_regex('Pad\/APad')
111
+
112
+ # Some UA contain the fragment 'Android; Tablet;' or 'Opera Tablet', so we assume those devices
113
+ # as tablets
114
+ t = 'tablet' if t.nil? && (android_tablet_fragment? || opera_tablet?)
115
+
116
+ # Some user agents simply contain the fragment 'Android; Mobile;', so we assume those devices
117
+ # as smartphones
118
+ t = 'smartphone' if t.nil? && android_mobile_fragment?
119
+
120
+ # Some UA contains the 'Android; Mobile VR;' fragment
121
+ t = 'wearable' if t.nil? && android_vr_fragment?
122
+
58
123
  # Android up to 3.0 was designed for smartphones only. But as 3.0,
59
124
  # which was tablet only, was published too late, there were a
60
125
  # bunch of tablets running with 2.x With 4.0 the two trees were
@@ -63,46 +128,64 @@ class DeviceDetector
63
128
  # So were are expecting that all devices running Android < 2 are
64
129
  # smartphones Devices running Android 3.X are tablets. Device type
65
130
  # of Android 2.X and 4.X+ are unknown
66
- if t.nil? && os.short_name == 'AND' && os.full_version && !os.full_version.empty?
67
- if os.full_version < '2'
131
+ if t.nil? && os_name == 'Android' && os.full_version && !os.full_version.empty?
132
+ full_version = Gem::Version.new(os.full_version)
133
+ if full_version < VersionExtractor::MAJOR_VERSION_2
68
134
  t = 'smartphone'
69
- elsif os.full_version >= '3' && os.full_version < '4'
135
+ elsif full_version >= VersionExtractor::MAJOR_VERSION_3 && \
136
+ full_version < VersionExtractor::MAJOR_VERSION_4
70
137
  t = 'tablet'
71
138
  end
72
139
  end
73
140
 
74
141
  # All detected feature phones running android are more likely a smartphone
75
- if t == 'feature phone' && os.family == 'Android'
76
- t = 'smartphone'
77
- end
142
+ t = 'smartphone' if t == 'feature phone' && os_family == 'Android'
143
+
144
+ # All unknown devices under running Java ME are more likely a features phones
145
+ t = 'feature phone' if t.nil? && os_name == 'Java ME'
78
146
 
79
147
  # According to http://msdn.microsoft.com/en-us/library/ie/hh920767(v=vs.85).aspx
80
- # Internet Explorer 10 introduces the "Touch" UA string token. If this token is present at the end of the
81
- # UA string, the computer has touch capability, and is running Windows 8 (or later).
148
+ # Internet Explorer 10 introduces the "Touch" UA string token. If this token is present at the
149
+ # end of the UA string, the computer has touch capability, and is running Windows 8 (or later).
82
150
  # This UA string will be transmitted on a touch-enabled system running Windows 8 (RT)
83
151
  #
84
- # As most touch enabled devices are tablets and only a smaller part are desktops/notebooks we assume that
85
- # all Windows 8 touch devices are tablets.
152
+ # As most touch enabled devices are tablets and only a smaller part are desktops/notebooks we
153
+ # assume that all Windows 8 touch devices are tablets.
86
154
  if t.nil? && touch_enabled? &&
87
- (os.short_name == 'WRT' || (os.short_name == 'WIN' && os.full_version && os.full_version >= '8'))
155
+ (os_name == 'Windows RT' ||
156
+ (os_name == 'Windows' && os_full_version &&
157
+ Gem::Version.new(os_full_version) >= VersionExtractor::MAJOR_VERSION_8))
88
158
  t = 'tablet'
89
159
  end
90
160
 
91
- if opera_tv_store?
92
- t = 'tv'
93
- end
161
+ # All devices running Opera TV Store are assumed to be a tv
162
+ t = 'tv' if opera_tv_store?
94
163
 
95
- if t.nil? && ['Kylo', 'Espial TV Browser'].include?(client.name)
164
+ # All devices that contain Andr0id in string are assumed to be a tv
165
+ if user_agent =~ build_regex('Andr0id|(?:Android(?: UHD)?|Google) TV|\(lite\) TV|BRAVIA')
96
166
  t = 'tv'
97
167
  end
98
168
 
99
- # set device type to desktop for all devices running a desktop os that were
100
- # not detected as an other device type
101
- if t.nil? && os.desktop? && !puffin_browser?
102
- t = 'desktop'
103
- end
169
+ # All devices running Tizen TV or SmartTV are assumed to be a tv
170
+ t = 'tv' if t.nil? && tizen_samsung_tv?
171
+
172
+ # Devices running those clients are assumed to be a TV
173
+ t = 'tv' if ['Kylo', 'Espial TV Browser', 'LUJO TV Browser', 'LogicUI TV Browser',
174
+ 'Open TV Browser', 'Seraphic Sraf', 'Opera Devices', 'Crow Browser',
175
+ 'Vewd Browser', 'TiviMate', 'Quick Search TV', 'QJY TV Browser',
176
+ 'TV Bro'].include?(name)
177
+
178
+ # All devices containing TV fragment are assumed to be a tv
179
+ t = 'tv' if t.nil? && user_agent =~ build_regex('\(TV;')
180
+
181
+ has_desktop = t != 'desktop' && desktop_string? && desktop_fragment?
182
+ t = 'desktop' if has_desktop
183
+
184
+ # set device type to desktop for all devices running a desktop os that were not detected as
185
+ # another device type
186
+ return t if t || !desktop?
104
187
 
105
- t
188
+ 'desktop'
106
189
  end
107
190
 
108
191
  def known?
@@ -118,7 +201,6 @@ class DeviceDetector
118
201
  end
119
202
 
120
203
  class << self
121
-
122
204
  class Configuration
123
205
  attr_accessor :max_cache_keys
124
206
 
@@ -137,11 +219,10 @@ class DeviceDetector
137
219
  @cache ||= MemoryCache.new(config.to_hash)
138
220
  end
139
221
 
140
- def configure(&block)
222
+ def configure
141
223
  @config = Configuration.new
142
224
  yield(config)
143
225
  end
144
-
145
226
  end
146
227
 
147
228
  private
@@ -162,12 +243,63 @@ class DeviceDetector
162
243
  @os ||= OS.new(user_agent)
163
244
  end
164
245
 
246
+ # https://github.com/matomo-org/device-detector/blob/67ae11199a5129b42fa8b985d372ea834104fe3a/DeviceDetector.php#L931-L938
247
+ def fake_ua?
248
+ device.brand == 'Apple' && !DeviceDetector::OS::APPLE_OS_NAMES.include?(os_name)
249
+ end
250
+
251
+ # https://github.com/matomo-org/device-detector/blob/be1c9ef486c247dc4886668da5ed0b1c49d90ba8/Parser/Client/Browser.php#L772
252
+ # Fix mobile browser names e.g. Chrome => Chrome Mobile
253
+ def mobile_fix?
254
+ client.name == "#{client_hint.browser_name} Mobile"
255
+ end
256
+
257
+ def linux_fix?
258
+ client_hint.platform == 'Linux' &&
259
+ %w[iOS Android].include?(os.name) &&
260
+ %w[?0 0].include?(client_hint.mobile)
261
+ end
262
+
263
+ # Related to issue mentionned in device.rb#1562
264
+ def fix_for_x_music
265
+ user_agent&.include?('X-music Ⅲ') ? 'X-Music III' : nil
266
+ end
267
+
268
+ def pico_os_fix?
269
+ client_hint.os_name == 'Pico OS'
270
+ end
271
+
272
+ # https://github.com/matomo-org/device-detector/blob/323629cb679c8572a9745cba9c3803fee13f3cf6/Parser/OperatingSystem.php#L398-L403
273
+ def fire_os_fix?
274
+ !client_hint.platform.nil? && os.name == 'Fire OS'
275
+ end
276
+
277
+ def fire_os_version
278
+ DeviceDetector::OS
279
+ .mapped_os_version(client_hint.os_version, DeviceDetector::OS::FIRE_OS_VERSION_MAPPING)
280
+ end
281
+
282
+ # https://github.com/matomo-org/device-detector/blob/323629cb679c8572a9745cba9c3803fee13f3cf6/Parser/OperatingSystem.php#L378-L383
283
+ def skip_os_version?
284
+ !client_hint.os_family.nil? &&
285
+ client_hint.os_version.nil? &&
286
+ client_hint.os_family != os.family
287
+ end
288
+
165
289
  def android_tablet_fragment?
166
- user_agent =~ build_regex('Android; Tablet;')
290
+ user_agent =~ build_regex('Android( [\.0-9]+)?; Tablet;|Tablet(?! PC)|.*\-tablet$')
167
291
  end
168
292
 
169
293
  def android_mobile_fragment?
170
- user_agent =~ build_regex('Android; Mobile;')
294
+ user_agent =~ build_regex('Android( [\.0-9]+)?; Mobile;|.*\-mobile$')
295
+ end
296
+
297
+ def android_vr_fragment?
298
+ user_agent =~ build_regex('Android( [\.0-9]+)?; Mobile VR;| VR ')
299
+ end
300
+
301
+ def desktop_fragment?
302
+ user_agent =~ build_regex('Desktop(?: (x(?:32|64)|WOW64))?;')
171
303
  end
172
304
 
173
305
  def touch_enabled?
@@ -175,20 +307,36 @@ class DeviceDetector
175
307
  end
176
308
 
177
309
  def opera_tv_store?
178
- user_agent =~ build_regex('Opera TV Store')
310
+ user_agent =~ build_regex('Opera TV Store|OMI/')
179
311
  end
180
312
 
181
313
  def opera_tablet?
182
314
  user_agent =~ build_regex('Opera Tablet')
183
315
  end
184
316
 
317
+ def tizen_samsung_tv?
318
+ user_agent =~ build_regex('SmartTV|Tizen.+ TV .+$')
319
+ end
320
+
321
+ def uses_mobile_browser?
322
+ client.browser? && client.mobile_only_browser?
323
+ end
324
+
185
325
  # This is a workaround until we support detecting mobile only browsers
186
- def puffin_browser?
187
- client.name == 'Puffin'
326
+ def desktop_string?
327
+ user_agent =~ /Desktop/
328
+ end
329
+
330
+ def desktop?
331
+ return false if os_name.nil? || os_name == '' || os_name == 'UNK'
332
+
333
+ # Check for browsers available for mobile devices only
334
+ return false if uses_mobile_browser?
335
+
336
+ DeviceDetector::OS::DESKTOP_OSS.include?(os_family)
188
337
  end
189
338
 
190
339
  def build_regex(src)
191
340
  Regexp.new('(?:^|[^A-Z0-9\_\-])(?:' + src + ')', Regexp::IGNORECASE)
192
341
  end
193
-
194
342
  end