browser_sniffer 2.4.0 → 2.5.2

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4bb83754d8d9d227841406041cb455e973554dde6b74a94288ee0e4bc8bce0fb
4
- data.tar.gz: 690cb4204124ede02eb0f8d20ecda5a81be5d41e599b8b795fd685d4775eb05b
3
+ metadata.gz: f1fa6f0ef701270fad08c72634ccbb5c9a01a8748eb5368ccff0d62066065c9a
4
+ data.tar.gz: 7915b883d825b1776b6833ea89cdde0be71ebed046fd06e73627cdc6b52c07ad
5
5
  SHA512:
6
- metadata.gz: 48984712d0918c8202493f1ace3027ae4f02abfadc092b7102d6c9158e375d0553f4550d87f3a6d8d3b0ad90df98ced731fa02b2edab7b72ce9141120fc335c9
7
- data.tar.gz: 5c5b4cf4cac87fbd8e928e5d4ab720660e55d06e0af9175fddf61d7febb2324f7bf5b47855c2586f48f4de17c7190a75ea0b64344dfc74828abce41d2934cf21
6
+ metadata.gz: 53940f3356cabe18354c764930c2acbfb0e09c884147d63e5556f139c41df51a0c437fa05b77bf50a10e117e6d3aec2a8f2e4450be2f762a909e2ac9f64eb8ab
7
+ data.tar.gz: 6cb0b817ae2c25c0163d7a70e6db1952878ce2a96d19981d158b0de53d45943d587584ff64e66076c1b5e5d68cfc47ad7e2c220babff87e4ffe5c0aba4f3efa1
@@ -0,0 +1,6 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: github-actions
4
+ directory: "/"
5
+ schedule:
6
+ interval: weekly
@@ -19,9 +19,9 @@ jobs:
19
19
  - 3.2
20
20
  - jruby
21
21
  steps:
22
- - uses: actions/checkout@v3
22
+ - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
23
23
  - name: Set up Ruby ${{ matrix.version }}
24
- uses: ruby/setup-ruby@v1
24
+ uses: ruby/setup-ruby@e34163cd15f4bb403dcd72d98e295997e6a55798 # v1.238.0
25
25
  with:
26
26
  bundler-cache: true
27
27
  ruby-version: ${{ matrix.version }}
data/.gitignore CHANGED
@@ -3,7 +3,6 @@
3
3
  .bundle
4
4
  .config
5
5
  .yardoc
6
- Gemfile.lock
7
6
  InstalledFiles
8
7
  _yardoc
9
8
  coverage
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 3.1.2
data/Gemfile.lock ADDED
@@ -0,0 +1,24 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ browser_sniffer (2.5.2)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ minitest (5.22.3)
10
+ rake (13.1.0)
11
+
12
+ PLATFORMS
13
+ arm64-darwin-21
14
+ universal-java-11
15
+ x86_64-linux
16
+
17
+ DEPENDENCIES
18
+ browser_sniffer!
19
+ bundler
20
+ minitest
21
+ rake
22
+
23
+ BUNDLED WITH
24
+ 2.3.7
data/dev.yml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: BrowserSniffer
3
3
  up:
4
- - ruby: 3.1.2
4
+ - ruby
5
5
  - bundler
6
6
  commands:
7
7
  test: bundle exec rake test
@@ -90,9 +90,12 @@ class BrowserSniffer
90
90
  # Shopify Ping iOS
91
91
  %r{.*(Shopify Ping)\/(?:iPhone\sOS|iOS)\/([\d\.]+) \((iPhone|iPad|iPod)}i
92
92
  ], [[:name, 'Shopify Ping'], :version], [
93
+ # Shopify CLI
94
+ %r{.*(Shopify CLI)(?:; v=([\d\.]+))?}i
95
+ ], [[:name, 'Shopify CLI'], :version], [
93
96
  # Presto based
94
97
  /(opera\smini)\/((\d+)?[\w\.-]+)/i, # Opera Mini
95
- /(opera\s[mobiletab]+).+:version\/((\d+)?[\w\.-]+)/i, # Opera Mobi/Tablet
98
+ /(opera\s(mobile|tab)).+:version\/((\d+)?[\w\.-]+)/i, # Opera Mobi/Tablet
96
99
  /(opera).+:version\/((\d+)?[\w\.]+)/i, # Opera > 9.80
97
100
  /(opera)[\/\s]+((\d+)?[\w\.]+)/i # Opera < 9.80
98
101
  ], [:name, :version, :major, [:type, :opera]], [
@@ -106,8 +109,6 @@ class BrowserSniffer
106
109
  ], [:name, :version, :major], [
107
110
  /(?:ms|\()(ie)\s((\d+)[\w\.]+)/i # Internet Explorer
108
111
  ], [:name, :version, :major, [:type, :ie]], [
109
- /Edge\/(\d+).\d+/i #Edge
110
- ], [:major, [:version, 10], [:name, 'Edge'], [:type, :edge]], [
111
112
  /Mozilla\/5.0.*Windows NT 6\.\d.*Trident\/7\.\d.*rv:(\d+)\.\d*/i #IE11 on Win7
112
113
  ], [:major, [:version, 7], [:name, 'Internet Explorer'], [:type, :ie]], [
113
114
  /Mozilla\/5.0.*Windows NT 10\.\d.*Trident\/7\.\d.*rv:(\d+)\.\d*.*like\sGecko/i #IE11 on Win10
@@ -116,10 +117,16 @@ class BrowserSniffer
116
117
  /(rekonq)\/?((\d+)[\w\.]+)*/i, # Rekonq
117
118
  /(flock|rockmelt|midori|epiphany|silk|skyfire|ovibrowser|bolt)\/((\d+)?[\w\.-]+)/i # Chromium/Flock/RockMelt/Midori/Epiphany/Silk/Skyfire/Bolt
118
119
  ], [:name, :version, :major], [
119
- /(yabrowser)\/((\d+)?[\w\.]+)/i # Yandex
120
- ], [[:name, 'Yandex'], :version, :major], [
120
+ /(yabrowser)\/((\d+)?[\w\.]+)/i # Yandex Browser
121
+ ], [[:name, 'Yandex Browser'], :version, :major, [:type, :yandex]], [
121
122
  /(comodo_dragon)\/((\d+)?[\w\.]+)/i # Comodo Dragon
122
123
  ], [[:name, 'Comodo Dragon'], :version, :major], [
124
+ /(vivaldi)\/((\d+)?[\w\.]+)/i # Vivaldi
125
+ ], [[:name, 'Vivaldi'], :version, :major, [:type, :vivaldi]], [
126
+ /(brave)\/((\d+)?[\w\.]+)/i # Brave
127
+ ], [[:name, 'Brave'], :version, :major, [:type, :brave]], [
128
+ /(?:Edge|Edg)\/(\d+).\d+/i # Edge (must come before Chrome patterns)
129
+ ], [:major, [:version, 10], [:name, 'Edge'], [:type, :edge]], [
123
130
  /(chromium)\/((\d+)?[\w\.-]+)/i, # Chromium
124
131
  /(chrome)\/v?((\d+)?[\w\.]+)/i # Chrome
125
132
  ], [:name, :version, :major, [:type, :chrome]], [
@@ -156,7 +163,7 @@ class BrowserSniffer
156
163
  /(uc\s?browser|polaris|lynx|dillo|icab|doris|amaya|w3m|netsurf|word|excel)[\/\s]?((\d+)?[\w\.]+)/i, # UCBrowser/Polaris/Lynx/Dillo/iCab/Doris/Amaya/w3m/NetSurf/Word/Excel
157
164
  /(links)\s\(((\d+)?[\w\.]+)/i, # Links
158
165
  /(gobrowser)\/?((\d+)?[\w\.]+)*/i, # GoBrowser
159
- /(ice\s?browser)\/v?((\d+)?[\w\._]+)/i, # ICE Browser
166
+ /(ice\s?browser)\/v?((\d+)?[\w\.]+)/i, # ICE Browser
160
167
  /(mosaic)[\/\s]((\d+)?[\w\.]+)/i # Mosaic
161
168
  ], [:name, :version, :major]
162
169
  ],
@@ -215,13 +222,64 @@ class BrowserSniffer
215
222
  %r{.*Shopify POS\/[\d\.]+ \((iPod touch)\;.*Scale/([\d\.]+)}i,
216
223
  ], [[:type, :handheld], :scale], [
217
224
  # Shopify POS for Android (SmartWebView)
218
- %r{.*Shopify\sPOS.*\(.*(Android)\s[\d\.]+\;\s(.*)\sBuild/.*\)\sPOS.*[\d+\.]+}i,
225
+ %r{.*Shopify\sPOS[^\(]*\((?:[^()]*)(Android)\s[\d\.]+\;\s(?:[^;()]*\;\s)*([^;()]*)\sBuild/[^)]*\)\sPOS[^\d]*[\d+\.]+}i,
219
226
  ], [[:type, :handheld], :model], [
220
227
  # Shopify POS for Android (Native App)
221
- %r{.*\(.*(Android)\s[\d\.]+\;\s(.*)\sBuild/.*\)\sPOS.*[\d+\.]+}i,
228
+ %r{.*\((?:[^()]*)(Android)\s[\d\.]+\;\s(?:[^;()]*\;\s)*([^;()]*)\sBuild/[^)]*\)\sPOS[^\d]*[\d+\.]+}i,
222
229
  ], [[:type, :handheld], :model], [
230
+ # New tablet patterns (must come early to avoid generic matches)
231
+ # Xiaomi Tablets
232
+ /android.+;\s((?:redmi\s+pad|(?:redmi\s*)?mi[\s\-]*pad)[^;]*?)\s*build/i,
233
+ # Xiaomi tablet numeric codes (pad models like 24091RPADG)
234
+ /android.+;\s(\d{5}RPAD[A-Z])\s*build/i
235
+ ], [:model, [:vendor, 'Xiaomi'], [:type, :tablet]], [
236
+ # Huawei Tablets
237
+ /android.+;\s(mediapad[^;]*?)\s*build/i,
238
+ /android.+;\s((?:ag[rs][2356]?k?|bah[234]?|bg[2o]|bt[kv]|cmr|cpn|db[ry]2?|jdn2|got|kob2?k?|mon|pce|scm|sht?|[tw]gr|vrd)-[ad]?[lw][0125][09]b?)\s*build/i
239
+ ], [:model, [:vendor, 'Huawei'], [:type, :tablet]], [
240
+ # Honor Tablets
241
+ /android.+;\s((?:brt|eln|hey2?|gdi|jdn)-[^\s;]*)\s*build/i
242
+ ], [:model, [:vendor, 'Honor'], [:type, :tablet]], [
243
+ # OPPO Tablets
244
+ /android.+;\s(opd2\d{3}a?)\s*build/i
245
+ ], [:model, [:vendor, 'OPPO'], [:type, :tablet]], [
246
+ # Huawei Mobile (must come before generic patterns)
247
+ /android.+;\s((?:hbp|alm|ane|art|ata|bla|col|cro|dub|ele|emm|eva|hma|hry|lya|mar|nce|pot|sne|vce|vne|yal)-[lx][0-9x]+)\s+build/i
248
+ ], [:model, [:vendor, 'Huawei'], [:type, :handheld]], [
249
+ # Lenovo tablets
250
+ /android.+;\s(let\d+[^;]*?)\s*build/i
251
+ ], [:model, [:vendor, 'Lenovo'], [:type, :tablet]], [
252
+ # Onyx e-reader devices
253
+ /android.+;\s(note(?:air|pro)[^;]*?)\s*build/i
254
+ ], [:model, [:vendor, 'Onyx'], [:type, :tablet]], [
255
+ # New mobile patterns (after tablets but before generic patterns)
256
+ # Xiaomi Mobile
257
+ /android.+;\s(poco[^;]*?)\s*build/i,
258
+ /android.+;\s((?:redmi[^;]*?|mi[\s\d][^;]*?))\s*build/i,
259
+ # Xiaomi numeric model codes (like 24091RPADG)
260
+ /android.+;\s(\d{5}[a-z]+[^;]*?)\s*build/i
261
+ ], [:model, [:vendor, 'Xiaomi'], [:type, :handheld]], [
262
+ # OnePlus Mobile
263
+ /android.+;\s(oneplus[^;]*?)\s*build/i
264
+ ], [:model, [:vendor, 'OnePlus'], [:type, :handheld]], [
265
+ # Realme Mobile
266
+ /android.+;\s(realme[^;]*?)\s*build/i
267
+ ], [:model, [:vendor, 'Realme'], [:type, :handheld]], [
268
+ # Vivo Mobile
269
+ /android.+;\s(vivo[^;]*?)\s*build/i
270
+ ], [:model, [:vendor, 'Vivo'], [:type, :handheld]], [
271
+ # OPPO Mobile
272
+ /android.+;\s(oppo[^;]*?)\s*build/i
273
+ ], [:model, [:vendor, 'OPPO'], [:type, :handheld]], [
274
+ # Google Pixel devices
275
+ /android.+;\s(pixel[\w\s]*pro[^;]*?)\s+build/i,
276
+ /android.+;\s(pixel[\w\s]*[^;]*?)\s+build/i
277
+ ], [:model, [:vendor, 'Google'], [:type, :handheld]], [
223
278
  /\((playbook);[\w\s\);-]+(rim)/i # PlayBook
224
279
  ], [:model, :vendor, [:type, :tablet]], [
280
+ # iPad with modern device identifier format (iPad15,x iPad16,x etc)
281
+ /\((ipad\d+,\d+);[^)]*\)/i
282
+ ], [[:model, 'iPad'], [:vendor, 'Apple'], [:name, :ipad], [:type, :tablet]], [
225
283
  /\((ipad);[\w\s\);-]+(apple)/i # iPad
226
284
  ], [:model, :vendor, [:name, :ipad], [:type, :tablet]], [
227
285
  /(hp).+(touchpad)/i, # HP TouchPad
@@ -229,6 +287,9 @@ class BrowserSniffer
229
287
  /\s(nook)[\w\s]+build\/(\w+)/i, # Nook
230
288
  /(dell)\s(strea[kpr\s\d]*[\dko])/i # Dell Streak
231
289
  ], [:vendor, :model, [:type, :tablet]], [
290
+ # iPhone with modern device identifier format (iPhone17,x etc)
291
+ /\((iphone\d+,\d+);[^)]*\)/i
292
+ ], [[:model, 'iPhone'], [:vendor, 'Apple'], [:name, :iphone], [:type, :handheld]], [
232
293
  /\((?:iphone|ipod(?: touch)?);.+(apple)/i # iPod Touch/iPhone
233
294
  ], [:model, :vendor, [:name, :iphone], [:type, :handheld]], [
234
295
  /(blackberry)[\s-]?(\w+)/i, # BlackBerry
@@ -242,28 +303,56 @@ class BrowserSniffer
242
303
  ], [[:vendor, 'Asus'], :model, [:type, :tablet]], [
243
304
  /(sony)\s(tablet\s[ps])/i # Sony Tablets
244
305
  ], [:vendor, :model, [:type, :tablet]], [
245
- /(nintendo)\s([wids3u]+)/i # Nintendo
306
+ /(nintendo)\s+(switch|wii\s*u?|3?ds)/i # Nintendo (improved model parsing)
246
307
  ], [:vendor, :model, [:type, :console]], [
247
- /((playstation)\s[3portablevi]+)/i # Playstation
308
+ # PlayStation Vita
309
+ /(playstation vita)\s+[\d\.]+/i
310
+ ], [[:model, 'PlayStation Vita'], [:vendor, 'Sony'], [:type, :console]], [
311
+ /((playstation)\s[3portablevi]+)/i # Playstation (generic)
248
312
  ], [[:vendor, 'Sony'], :model, [:type, :console]], [
313
+ # PlayStation 4
314
+ /(playstation 4)\s+[\d\.]+/i
315
+ ], [[:model, 'PlayStation 4'], [:vendor, 'Sony'], [:type, :console]], [
316
+ # PlayStation 5
317
+ /playstation;\s+(playstation 5)\/[\d\.]+/i
318
+ ], [[:model, 'PlayStation 5'], [:vendor, 'Sony'], [:type, :console]], [
319
+ # Xbox Series X/S (Windows-based)
320
+ /windows nt .+;\s+xbox;\s+(xbox series [xs])/i
321
+ ], [[:model, 'Xbox Series X'], [:vendor, 'Microsoft'], [:type, :console]], [
322
+ # Xbox One (Windows-based)
323
+ /windows nt .+;\s+xbox_one_ed/i
324
+ ], [[:model, 'Xbox One'], [:vendor, 'Microsoft'], [:type, :console]], [
325
+ # Xbox One (Windows Phone-based)
326
+ /windows phone .+;\s+xbox;\s+(xbox one)/i
327
+ ], [:model, [:vendor, 'Microsoft'], [:type, :console]], [
328
+ # Xbox Series X/S (legacy format)
329
+ /\(xbox;\s+(xbox series [xs])\)/i
330
+ ], [:model, [:vendor, 'Microsoft'], [:type, :console]], [
331
+ # Xbox One (legacy format)
332
+ /\(xbox;\s+(xbox one)\)/i
333
+ ], [:model, [:vendor, 'Microsoft'], [:type, :console]], [
249
334
  /(sprint\s(\w+))/i # Sprint Phones
250
335
  ], [:vendor, :model, [:type, :handheld]], [
251
- /(htc)[;_\s-]+([\w\s_]+(?=\))|\w+)*/i, # HTC
336
+ /(htc)[;_\s-]+([\w\s]+(?=\))|\w+)*/i, # HTC
252
337
  /(zte)-(\w+)*/i, # ZTE
253
338
  /(alcatel|geeksphone|huawei|lenovo|nexian|panasonic|(?=;\s)sony)[_\s-]?([\w-]+)*/i # Alcatel/GeeksPhone/Huawei/Lenovo/Nexian/Panasonic/Sony
254
339
  ], [:vendor, [:model, lambda {|str| str && str.gsub(/_/, ' ') }], [:type, :handheld]], [
340
+ # Modern Motorola devices (must come before generic Motorola pattern)
341
+ /android.+;\s(moto\s+[^;]*?)\s*build/i
342
+ ], [:model, [:vendor, 'Motorola'], [:type, :handheld]], [
255
343
  /\s((milestone|droid[2x]?))[globa\s]*\sbuild\//i, # Motorola
256
344
  /(mot)[\s-]?(\w+)*/i
257
345
  ], [[:vendor, 'Motorola'], :model, [:type, :handheld]], [
258
346
  /android.+\s((mz60\d|xoom[\s2]{0,2}))\sbuild\//i
259
347
  ], [[:vendor, 'Motorola'], :model, [:type, :tablet]], [
260
- /android.+((sch-i[89]0\d|shw-m380s|gt-p\d{4}|gt-n8000|sgh-t8[56]9))/i
348
+ /android.+((sch-i[89]0\d|shw-m380s|sm-[ptx]\w{2,4}|gt-[pn]\d{2,4}|sgh-t8[56]9|nexus 10))/i
261
349
  ], [[:vendor, 'Samsung'], :model, [:type, :tablet]], [
262
350
  # Samsung
351
+ /android.+;\s(sm-s[^;)\s]*)/i, # Samsung Galaxy S-series phones
263
352
  /((s[cgp]h-\w+|gt-\w+|galaxy\snexus))/i,
264
353
  /(sam[sung]*)[\s-]*(\w+-?[\w-]*)*/i,
265
354
  /sec-((sgh\w+))/i
266
- ], [[:vendor, 'Samsung'], :model, [:type, :handheld]], [
355
+ ], [:model, [:vendor, 'Samsung'], [:type, :handheld]], [
267
356
  /(sie)-(\w+)*/i # Siemens
268
357
  ], [[:vendor, 'Siemens'], :model, [:type, :handheld]], [
269
358
  /(maemo|nokia).*(n900|lumia\s\d+)/i, # Nokia
@@ -273,9 +362,40 @@ class BrowserSniffer
273
362
  ], [[:vendor, 'Acer'], :model, [:type, :tablet]], [
274
363
  /android\s3\.[-\s\w;]{10}(lg?)-([06cv9]{3,4})/i # LG
275
364
  ], [[:vendor, 'LG'], :model, [:type, :tablet]], [
365
+ # Xiaomi Tablets (must come before generic mobile patterns)
366
+ /android.+;\s((?:red)?mi[\s\-]*pad[\w\s]*)\sbuild/i
367
+ ], [[:vendor, 'Xiaomi'], :model, [:type, :tablet]], [
368
+ # Huawei Tablets (must come before generic mobile patterns)
369
+ /android.+;\s(mediapad[\w\s]*)\sbuild/i,
370
+ /android.+;\s((?:ag[rs][2356]?k?|bah[234]?|bg[2o]|bt[kv]|cmr|cpn|db[ry]2?|jdn2|got|kob2?k?|mon|pce|scm|sht?|[tw]gr|vrd)-[ad]?[lw][0125][09]b?)\sbuild/i
371
+ ], [[:vendor, 'Huawei'], :model, [:type, :tablet]], [
372
+ # Honor Tablets (must come before generic mobile patterns)
373
+ /android.+;\s((?:brt|eln|hey2?|gdi|jdn)-a?[lnw]09|(?:ag[rm]3?|jdn2|kob2)-a?[lw]0[09]hn)\sbuild/i
374
+ ], [[:vendor, 'Honor'], :model, [:type, :tablet]], [
375
+ # OPPO Tablets (must come before generic mobile patterns)
376
+ /android.+;\s(opd2\d{3}a?)\sbuild/i
377
+ ], [[:vendor, 'OPPO'], :model, [:type, :tablet]], [
276
378
  /((nexus\s4))/i,
277
379
  /(lg)[-e;\s\/]+(\w+)*/i
278
380
  ], [[:vendor, 'LG'], :model, [:type, :handheld]], [
381
+ # Amazon Fire TV
382
+ /android.+;\s(aft[a-z0-9]+)(?:\s+build|\))/i
383
+ ], [:model, [:vendor, 'Amazon'], [:type, :console]], [
384
+ # Amazon Fire tablets
385
+ /android.+;\s(kf[a-z]+[^;]*?)\s*build/i
386
+ ], [:model, [:vendor, 'Amazon'], [:type, :tablet]], [
387
+ # Apple TV
388
+ /appletv(\d+,\d+)\/([\d\.]+)/i
389
+ ], [[:model, 'Apple TV'], [:vendor, 'Apple'], [:type, :console]], [
390
+ # Chromecast
391
+ /crkey\s+([^)]+)/i
392
+ ], [[:model, 'Chromecast'], [:vendor, 'Google'], [:type, :console]], [
393
+ # Google ADT (Android TV Developer)
394
+ /android.+;\s(adt-\d+)\s+build/i
395
+ ], [:model, [:vendor, 'Google'], [:type, :console]], [
396
+ # Roku devices
397
+ /roku(\w+)\/([^(]+)/i
398
+ ], [:model, [:vendor, 'Roku'], [:type, :console]], [
279
399
  /opera\smobi/i,
280
400
  /android/i
281
401
  ], [[:type, :handheld]], [
@@ -376,7 +496,7 @@ class BrowserSniffer
376
496
  # BSD based
377
497
  /\s([frentopc-]{0,4}bsd|dragonfly)\s?([\w\.]+)*/i # FreeBSD/NetBSD/OpenBSD/PC-BSD/DragonFly
378
498
  ], [:name, :version],[
379
- /(ip[honead]+)(?:.*os\s*([\w]+)*\slike\smac|;\sopera)/i # iOS
499
+ /(ip[honead]+)(?:.*os\s*([\w]+)\slike\smac|;\sopera)/i # iOS
380
500
  ], [[:name, 'iOS'], [:version, lambda {|str| str && str.gsub(/_/, '.') }], [:type, :ios]], [
381
501
  /(mac\sos\sx)\s?([\w\s\.]+\w)*/i # Mac OS
382
502
  ], [:name, [:version, lambda {|str| str && str.gsub(/_/, '.') }], [:type, :mac]], [
@@ -1,3 +1,3 @@
1
1
  class BrowserSniffer
2
- VERSION = "2.4.0"
2
+ VERSION = "2.5.2"
3
3
  end
@@ -2,10 +2,16 @@ require "browser_sniffer/version"
2
2
  require "browser_sniffer/patterns"
3
3
 
4
4
  class BrowserSniffer
5
+ MAX_USER_AGENT_LENGTH = 512
6
+
5
7
  attr_reader :user_agent
6
8
 
7
9
  def initialize(user_agent)
8
- @user_agent = user_agent
10
+ @user_agent = if user_agent.is_a?(String)
11
+ user_agent[0, MAX_USER_AGENT_LENGTH]
12
+ else
13
+ user_agent
14
+ end
9
15
  end
10
16
 
11
17
  def ios?
@@ -675,8 +675,834 @@ class BrowserSnifferTest < Minitest::Test
675
675
  :major_browser_version => 103,
676
676
  :in_app_browser => :instagram,
677
677
  },
678
+ :yandex_mac => {
679
+ :user_agent => "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 YaBrowser/24.4.5.506 Yowser/2.5 Safari/537.36",
680
+ :form_factor => :desktop,
681
+ :ios? => false,
682
+ :android? => false,
683
+ :desktop? => true,
684
+ :engine => :webkit,
685
+ :major_engine_version => 537,
686
+ :os => :mac,
687
+ :os_version => '10.15.7',
688
+ :browser => :yandex,
689
+ :major_browser_version => 24,
690
+ :in_app_browser => nil,
691
+ },
692
+ # New Samsung Tablet Patterns
693
+ :samsung_tablet_sm_p => {
694
+ :user_agent => "Mozilla/5.0 (Linux; Android 11; SM-P615 Build/RP1A.200720.012; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/91.0.4472.114 Safari/537.36",
695
+ :form_factor => :tablet,
696
+ :ios? => false,
697
+ :android? => true,
698
+ :desktop? => false,
699
+ :engine => :webkit,
700
+ :major_engine_version => 537,
701
+ :os => :android,
702
+ :os_version => "11",
703
+ :browser => :chrome,
704
+ :major_browser_version => 91,
705
+ :device_vendor => 'Samsung',
706
+ :device_name => 'SM-P615',
707
+ },
708
+ :samsung_tablet_gt_n => {
709
+ :user_agent => "Mozilla/5.0 (Linux; Android 10; GT-N8020 Build/QP1A.190711.020; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/89.0.4389.105 Safari/537.36",
710
+ :form_factor => :tablet,
711
+ :ios? => false,
712
+ :android? => true,
713
+ :desktop? => false,
714
+ :engine => :webkit,
715
+ :major_engine_version => 537,
716
+ :os => :android,
717
+ :os_version => "10",
718
+ :browser => :chrome,
719
+ :major_browser_version => 89,
720
+ :device_vendor => 'Samsung',
721
+ :device_name => 'GT-N8020',
722
+ },
723
+ # Xiaomi Devices
724
+ :xiaomi_tablet_mi_pad => {
725
+ :user_agent => "Mozilla/5.0 (Linux; Android 9; Mi Pad 4 Build/PKQ1.180904.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/77.0.3865.120 Safari/537.36",
726
+ :form_factor => :tablet,
727
+ :ios? => false,
728
+ :android? => true,
729
+ :desktop? => false,
730
+ :engine => :webkit,
731
+ :major_engine_version => 537,
732
+ :os => :android,
733
+ :os_version => "9",
734
+ :browser => :chrome,
735
+ :major_browser_version => 77,
736
+ :device_vendor => 'Xiaomi',
737
+ :device_name => 'Mi Pad 4',
738
+ },
739
+ :xiaomi_tablet_redmi_pad => {
740
+ :user_agent => "Mozilla/5.0 (Linux; Android 12; Redmi Pad Build/SP1A.210812.016; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/103.0.5060.129 Safari/537.36",
741
+ :form_factor => :tablet,
742
+ :ios? => false,
743
+ :android? => true,
744
+ :desktop? => false,
745
+ :engine => :webkit,
746
+ :major_engine_version => 537,
747
+ :os => :android,
748
+ :os_version => "12",
749
+ :browser => :chrome,
750
+ :major_browser_version => 103,
751
+ :device_vendor => 'Xiaomi',
752
+ :device_name => 'Redmi Pad',
753
+ },
754
+ :xiaomi_mobile_poco => {
755
+ :user_agent => "Mozilla/5.0 (Linux; Android 11; POCO X3 Pro Build/RKQ1.200826.002; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/90.0.4430.210 Mobile Safari/537.36",
756
+ :form_factor => :handheld,
757
+ :ios? => false,
758
+ :android? => true,
759
+ :desktop? => false,
760
+ :engine => :webkit,
761
+ :major_engine_version => 537,
762
+ :os => :android,
763
+ :os_version => "11",
764
+ :browser => :chrome,
765
+ :major_browser_version => 90,
766
+ :device_vendor => 'Xiaomi',
767
+ :device_name => 'POCO X3 Pro',
768
+ },
769
+ :xiaomi_mobile_mi_phone => {
770
+ :user_agent => "Mozilla/5.0 (Linux; Android 12; Mi 11 Build/SKQ1.210216.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/98.0.4758.102 Mobile Safari/537.36",
771
+ :form_factor => :handheld,
772
+ :ios? => false,
773
+ :android? => true,
774
+ :desktop? => false,
775
+ :engine => :webkit,
776
+ :major_engine_version => 537,
777
+ :os => :android,
778
+ :os_version => "12",
779
+ :browser => :chrome,
780
+ :major_browser_version => 98,
781
+ :device_vendor => 'Xiaomi',
782
+ :device_name => 'Mi 11',
783
+ },
784
+ # Huawei Tablets
785
+ :huawei_tablet_mediapad => {
786
+ :user_agent => "Mozilla/5.0 (Linux; Android 10; MediaPad M5 Build/HUAWEICMR-W09; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/88.0.4324.152 Safari/537.36",
787
+ :form_factor => :tablet,
788
+ :ios? => false,
789
+ :android? => true,
790
+ :desktop? => false,
791
+ :engine => :webkit,
792
+ :major_engine_version => 537,
793
+ :os => :android,
794
+ :os_version => "10",
795
+ :browser => :chrome,
796
+ :major_browser_version => 88,
797
+ :device_vendor => 'Huawei',
798
+ :device_name => 'MediaPad M5',
799
+ },
800
+ :huawei_tablet_matepad => {
801
+ :user_agent => "Mozilla/5.0 (Linux; Android 11; BAH3-W09 Build/HUAWEIBAH3-W09; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/92.0.4515.159 Safari/537.36",
802
+ :form_factor => :tablet,
803
+ :ios? => false,
804
+ :android? => true,
805
+ :desktop? => false,
806
+ :engine => :webkit,
807
+ :major_engine_version => 537,
808
+ :os => :android,
809
+ :os_version => "11",
810
+ :browser => :chrome,
811
+ :major_browser_version => 92,
812
+ :device_vendor => 'Huawei',
813
+ :device_name => 'BAH3-W09',
814
+ },
815
+ # Honor Tablets
816
+ :honor_tablet => {
817
+ :user_agent => "Mozilla/5.0 (Linux; Android 10; BRT-LX1 Build/HONORBRT-LX1; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/87.0.4280.141 Safari/537.36",
818
+ :form_factor => :tablet,
819
+ :ios? => false,
820
+ :android? => true,
821
+ :desktop? => false,
822
+ :engine => :webkit,
823
+ :major_engine_version => 537,
824
+ :os => :android,
825
+ :os_version => "10",
826
+ :browser => :chrome,
827
+ :major_browser_version => 87,
828
+ :device_vendor => 'Honor',
829
+ :device_name => 'BRT-LX1',
830
+ },
831
+ # OPPO Devices
832
+ :oppo_tablet => {
833
+ :user_agent => "Mozilla/5.0 (Linux; Android 11; OPD2101A Build/RP1A.201005.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/91.0.4472.114 Safari/537.36",
834
+ :form_factor => :tablet,
835
+ :ios? => false,
836
+ :android? => true,
837
+ :desktop? => false,
838
+ :engine => :webkit,
839
+ :major_engine_version => 537,
840
+ :os => :android,
841
+ :os_version => "11",
842
+ :browser => :chrome,
843
+ :major_browser_version => 91,
844
+ :device_vendor => 'OPPO',
845
+ :device_name => 'OPD2101A',
846
+ },
847
+ :oppo_mobile => {
848
+ :user_agent => "Mozilla/5.0 (Linux; Android 12; OPPO Find X5 Build/SP1A.210812.016; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/102.0.5005.125 Mobile Safari/537.36",
849
+ :form_factor => :handheld,
850
+ :ios? => false,
851
+ :android? => true,
852
+ :desktop? => false,
853
+ :engine => :webkit,
854
+ :major_engine_version => 537,
855
+ :os => :android,
856
+ :os_version => "12",
857
+ :browser => :chrome,
858
+ :major_browser_version => 102,
859
+ :device_vendor => 'OPPO',
860
+ :device_name => 'OPPO Find X5',
861
+ },
862
+ # OnePlus Mobile
863
+ :oneplus_mobile => {
864
+ :user_agent => "Mozilla/5.0 (Linux; Android 12; OnePlus 9 Pro Build/SKQ1.210216.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/99.0.4844.88 Mobile Safari/537.36",
865
+ :form_factor => :handheld,
866
+ :ios? => false,
867
+ :android? => true,
868
+ :desktop? => false,
869
+ :engine => :webkit,
870
+ :major_engine_version => 537,
871
+ :os => :android,
872
+ :os_version => "12",
873
+ :browser => :chrome,
874
+ :major_browser_version => 99,
875
+ :device_vendor => 'OnePlus',
876
+ :device_name => 'OnePlus 9 Pro',
877
+ },
878
+ :oneplus_nord => {
879
+ :user_agent => "Mozilla/5.0 (Linux; Android 11; OnePlus Nord Build/RKQ1.201022.002; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/88.0.4324.181 Mobile Safari/537.36",
880
+ :form_factor => :handheld,
881
+ :ios? => false,
882
+ :android? => true,
883
+ :desktop? => false,
884
+ :engine => :webkit,
885
+ :major_engine_version => 537,
886
+ :os => :android,
887
+ :os_version => "11",
888
+ :browser => :chrome,
889
+ :major_browser_version => 88,
890
+ :device_vendor => 'OnePlus',
891
+ :device_name => 'OnePlus Nord',
892
+ },
893
+ # Realme Mobile
894
+ :realme_mobile => {
895
+ :user_agent => "Mozilla/5.0 (Linux; Android 11; realme GT Build/RKQ1.201022.002; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/89.0.4389.105 Mobile Safari/537.36",
896
+ :form_factor => :handheld,
897
+ :ios? => false,
898
+ :android? => true,
899
+ :desktop? => false,
900
+ :engine => :webkit,
901
+ :major_engine_version => 537,
902
+ :os => :android,
903
+ :os_version => "11",
904
+ :browser => :chrome,
905
+ :major_browser_version => 89,
906
+ :device_vendor => 'Realme',
907
+ :device_name => 'realme GT',
908
+ },
909
+ # Vivo Mobile
910
+ :vivo_mobile => {
911
+ :user_agent => "Mozilla/5.0 (Linux; Android 12; vivo X70 Pro Build/SP1A.210812.003; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/94.0.4606.85 Mobile Safari/537.36",
912
+ :form_factor => :handheld,
913
+ :ios? => false,
914
+ :android? => true,
915
+ :desktop? => false,
916
+ :engine => :webkit,
917
+ :major_engine_version => 537,
918
+ :os => :android,
919
+ :os_version => "12",
920
+ :browser => :chrome,
921
+ :major_browser_version => 94,
922
+ :device_vendor => 'Vivo',
923
+ :device_name => 'vivo X70 Pro',
924
+ },
925
+ # New Browsers
926
+ :vivaldi_browser => {
927
+ :user_agent => "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.134 Safari/537.36 Vivaldi/5.3.2679.68",
928
+ :form_factor => :desktop,
929
+ :ios? => false,
930
+ :android? => false,
931
+ :desktop? => true,
932
+ :engine => :webkit,
933
+ :major_engine_version => 537,
934
+ :os => :windows,
935
+ :os_version => "10",
936
+ :browser => :vivaldi,
937
+ :browser_name => 'Vivaldi',
938
+ :major_browser_version => 5,
939
+ },
940
+ :brave_browser => {
941
+ :user_agent => "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36 Brave/1.41.100",
942
+ :form_factor => :desktop,
943
+ :ios? => false,
944
+ :android? => false,
945
+ :desktop? => true,
946
+ :engine => :webkit,
947
+ :major_engine_version => 537,
948
+ :os => :windows,
949
+ :os_version => "10",
950
+ :browser => :brave,
951
+ :browser_name => 'Brave',
952
+ :major_browser_version => 1,
953
+ },
954
+ # Samsung Galaxy S-series (Issue 1 fix)
955
+ :samsung_galaxy_s25 => {
956
+ :user_agent => "Mozilla/5.0 (Linux; Android 15; SM-S931B Build/AP3A.240905.015.A2; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/127.0.6533.103 Mobile Safari/537.36",
957
+ :form_factor => :handheld,
958
+ :ios? => false,
959
+ :android? => true,
960
+ :desktop? => false,
961
+ :engine => :webkit,
962
+ :major_engine_version => 537,
963
+ :os => :android,
964
+ :os_version => "15",
965
+ :browser => :chrome,
966
+ :major_browser_version => 127,
967
+ :device_vendor => 'Samsung',
968
+ :device_name => 'SM-S931B',
969
+ },
970
+ :samsung_galaxy_s24_ultra => {
971
+ :user_agent => "Mozilla/5.0 (Linux; Android 14; SM-S928B/DS) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.230 Mobile Safari/537.36",
972
+ :form_factor => :handheld,
973
+ :ios? => false,
974
+ :android? => true,
975
+ :desktop? => false,
976
+ :engine => :webkit,
977
+ :major_engine_version => 537,
978
+ :os => :android,
979
+ :os_version => "14",
980
+ :browser => :chrome,
981
+ :major_browser_version => 120,
982
+ :device_vendor => 'Samsung',
983
+ :device_name => 'SM-S928B/DS',
984
+ },
985
+ :samsung_galaxy_s23 => {
986
+ :user_agent => "Mozilla/5.0 (Linux; Android 13; SM-S911B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Mobile Safari/537.36",
987
+ :form_factor => :handheld,
988
+ :ios? => false,
989
+ :android? => true,
990
+ :desktop? => false,
991
+ :engine => :webkit,
992
+ :major_engine_version => 537,
993
+ :os => :android,
994
+ :os_version => "13",
995
+ :browser => :chrome,
996
+ :major_browser_version => 104,
997
+ :device_vendor => 'Samsung',
998
+ :device_name => 'SM-S911B',
999
+ },
1000
+ :samsung_galaxy_s22_5g => {
1001
+ :user_agent => "Mozilla/5.0 (Linux; Android 13; SM-S901B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Mobile Safari/537.36",
1002
+ :form_factor => :handheld,
1003
+ :ios? => false,
1004
+ :android? => true,
1005
+ :desktop? => false,
1006
+ :engine => :webkit,
1007
+ :major_engine_version => 537,
1008
+ :os => :android,
1009
+ :os_version => "13",
1010
+ :browser => :chrome,
1011
+ :major_browser_version => 112,
1012
+ :device_vendor => 'Samsung',
1013
+ :device_name => 'SM-S901B',
1014
+ },
1015
+ :google_pixel_9_pro => {
1016
+ :user_agent => "Mozilla/5.0 (Linux; Android 14; Pixel 9 Pro Build/AD1A.240418.003; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/124.0.6367.54 Mobile Safari/537.36",
1017
+ :form_factor => :handheld,
1018
+ :ios? => false,
1019
+ :android? => true,
1020
+ :desktop? => false,
1021
+ :engine => :webkit,
1022
+ :major_engine_version => 537,
1023
+ :os => :android,
1024
+ :os_version => "14",
1025
+ :browser => :chrome,
1026
+ :major_browser_version => 124,
1027
+ :device_vendor => 'Google',
1028
+ :device_name => 'Pixel 9 Pro',
1029
+ },
1030
+ :google_pixel_8_pro => {
1031
+ :user_agent => "Mozilla/5.0 (Linux; Android 15; Pixel 8 Pro Build/AP4A.250105.002; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/132.0.6834.163 Mobile Safari/537.36",
1032
+ :form_factor => :handheld,
1033
+ :ios? => false,
1034
+ :android? => true,
1035
+ :desktop? => false,
1036
+ :engine => :webkit,
1037
+ :major_engine_version => 537,
1038
+ :os => :android,
1039
+ :os_version => "15",
1040
+ :browser => :chrome,
1041
+ :major_browser_version => 132,
1042
+ :device_vendor => 'Google',
1043
+ :device_name => 'Pixel 8 Pro',
1044
+ },
1045
+ :google_pixel_7a => {
1046
+ :user_agent => "Mozilla/5.0 (Linux; Android 14; Pixel 7a Build/UQ1A.240105.004; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/120.0.6099.230 Mobile Safari/537.36",
1047
+ :form_factor => :handheld,
1048
+ :ios? => false,
1049
+ :android? => true,
1050
+ :desktop? => false,
1051
+ :engine => :webkit,
1052
+ :major_engine_version => 537,
1053
+ :os => :android,
1054
+ :os_version => "14",
1055
+ :browser => :chrome,
1056
+ :major_browser_version => 120,
1057
+ :device_vendor => 'Google',
1058
+ :device_name => 'Pixel 7a',
1059
+ },
1060
+ :google_pixel_6 => {
1061
+ :user_agent => "Mozilla/5.0 (Linux; Android 12; Pixel 6 Build/SQ3A.220705.003; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/103.0.5060.129 Mobile Safari/537.36",
1062
+ :form_factor => :handheld,
1063
+ :ios? => false,
1064
+ :android? => true,
1065
+ :desktop? => false,
1066
+ :engine => :webkit,
1067
+ :major_engine_version => 537,
1068
+ :os => :android,
1069
+ :os_version => "12",
1070
+ :browser => :chrome,
1071
+ :major_browser_version => 103,
1072
+ :device_vendor => 'Google',
1073
+ :device_name => 'Pixel 6',
1074
+ },
1075
+ :iphone_16_pro => {
1076
+ :user_agent => "Mozilla/5.0 (iPhone17,1; CPU iPhone OS 18_2_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 Mohegan Sun/4.7.4",
1077
+ :form_factor => :handheld,
1078
+ :ios? => true,
1079
+ :android? => false,
1080
+ :desktop? => false,
1081
+ :engine => :webkit,
1082
+ :major_engine_version => 605,
1083
+ :os => :ios,
1084
+ :os_version => "18.2.1",
1085
+ :browser => nil,
1086
+ :device_vendor => 'Apple',
1087
+ :device_name => 'iPhone',
1088
+ },
1089
+ :iphone_16e => {
1090
+ :user_agent => "Mozilla/5.0 (iPhone17,5; CPU iPhone OS 18_3_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 FireKeepers/1.7.0",
1091
+ :form_factor => :handheld,
1092
+ :ios? => true,
1093
+ :android? => false,
1094
+ :desktop? => false,
1095
+ :engine => :webkit,
1096
+ :major_engine_version => 605,
1097
+ :os => :ios,
1098
+ :os_version => "18.3.2",
1099
+ :browser => nil,
1100
+ :device_vendor => 'Apple',
1101
+ :device_name => 'iPhone',
1102
+ },
1103
+ :ipad_pro_11_5th => {
1104
+ :user_agent => "Mozilla/5.0 (iPad16,3; CPU OS 18_3_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 Tropicana_NJ/5.7.1",
1105
+ :form_factor => :tablet,
1106
+ :ios? => true,
1107
+ :android? => false,
1108
+ :desktop? => false,
1109
+ :engine => :webkit,
1110
+ :major_engine_version => 605,
1111
+ :os => :ios,
1112
+ :os_version => "18.3.2",
1113
+ :browser => nil,
1114
+ :device_vendor => 'Apple',
1115
+ :device_name => 'iPad',
1116
+ },
1117
+ :ipad_air_6th => {
1118
+ :user_agent => "Mozilla/5.0 (iPad15,4; CPU OS 17_6_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 BetMGM_NJ/5.7.1",
1119
+ :form_factor => :tablet,
1120
+ :ios? => true,
1121
+ :android? => false,
1122
+ :desktop? => false,
1123
+ :engine => :webkit,
1124
+ :major_engine_version => 605,
1125
+ :os => :ios,
1126
+ :os_version => "17.6.1",
1127
+ :browser => nil,
1128
+ :device_vendor => 'Apple',
1129
+ :device_name => 'iPad',
1130
+ },
1131
+ :edge_browser => {
1132
+ :user_agent => "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36 Edge/91.0.864.59",
1133
+ :form_factor => :desktop,
1134
+ :ios? => false,
1135
+ :android? => false,
1136
+ :desktop? => true,
1137
+ :engine => :webkit,
1138
+ :major_engine_version => 537,
1139
+ :os => :windows,
1140
+ :os_version => "10",
1141
+ :browser => :edge,
1142
+ :browser_name => 'Edge',
1143
+ :major_browser_version => 91,
1144
+ },
1145
+ :playstation_4 => {
1146
+ :user_agent => "Mozilla/5.0 (PlayStation 4 4.55) AppleWebKit/537.73 (KHTML, like Gecko)",
1147
+ :form_factor => :console,
1148
+ :ios? => false,
1149
+ :android? => false,
1150
+ :desktop? => false,
1151
+ :engine => :webkit,
1152
+ :major_engine_version => 537,
1153
+ :device_vendor => 'Sony',
1154
+ :device_name => 'PlayStation 4',
1155
+ },
1156
+ :playstation_5 => {
1157
+ :user_agent => "Mozilla/5.0 (PlayStation; PlayStation 5/2.26) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0 Safari/605.1.15",
1158
+ :form_factor => :console,
1159
+ :ios? => false,
1160
+ :android? => false,
1161
+ :desktop? => false,
1162
+ :engine => :webkit,
1163
+ :major_engine_version => 605,
1164
+ :browser => :safari,
1165
+ :major_browser_version => 13,
1166
+ :device_vendor => 'Sony',
1167
+ :device_name => 'PlayStation 5',
1168
+ },
1169
+ :xbox_series_x => {
1170
+ :user_agent => "Mozilla/5.0 (Xbox; Xbox Series X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.82 Safari/537.36 Edge/20.02",
1171
+ :form_factor => :console,
1172
+ :ios? => false,
1173
+ :android? => false,
1174
+ :desktop? => false,
1175
+ :engine => :webkit,
1176
+ :major_engine_version => 537,
1177
+ :browser => :edge,
1178
+ :major_browser_version => 20,
1179
+ :device_vendor => 'Microsoft',
1180
+ :device_name => 'Xbox Series X',
1181
+ },
1182
+ :xbox_one => {
1183
+ :user_agent => "Mozilla/5.0 (Xbox; Xbox One) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2486.75 Safari/537.36 Edge/13.10586",
1184
+ :form_factor => :console,
1185
+ :ios? => false,
1186
+ :android? => false,
1187
+ :desktop? => false,
1188
+ :engine => :webkit,
1189
+ :major_engine_version => 537,
1190
+ :browser => :edge,
1191
+ :major_browser_version => 13,
1192
+ :device_vendor => 'Microsoft',
1193
+ :device_name => 'Xbox One',
1194
+ },
1195
+ :xiaomi_numeric_model => {
1196
+ :user_agent => "Mozilla/5.0 (Linux; Android 14; 24091RPADG Build/UP1A.231005.007; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/124.0.6367.179 Mobile Safari/537.36",
1197
+ :form_factor => :tablet,
1198
+ :ios? => false,
1199
+ :android? => true,
1200
+ :desktop? => false,
1201
+ :engine => :webkit,
1202
+ :major_engine_version => 537,
1203
+ :os => :android,
1204
+ :os_version => "14",
1205
+ :browser => :chrome,
1206
+ :major_browser_version => 124,
1207
+ :device_vendor => 'Xiaomi',
1208
+ :device_name => '24091RPADG',
1209
+ },
1210
+ :lenovo_tablet => {
1211
+ :user_agent => "Mozilla/5.0 (Linux; Android 12; LET105G Build/SP1A.210812.016) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36",
1212
+ :form_factor => :tablet,
1213
+ :ios? => false,
1214
+ :android? => true,
1215
+ :desktop? => false,
1216
+ :engine => :webkit,
1217
+ :major_engine_version => 537,
1218
+ :os => :android,
1219
+ :os_version => "12",
1220
+ :browser => :chrome,
1221
+ :major_browser_version => 103,
1222
+ :device_vendor => 'Lenovo',
1223
+ :device_name => 'LET105G',
1224
+ },
1225
+ :motorola_moto_g => {
1226
+ :user_agent => "Mozilla/5.0 (Linux; Android 11; moto g power (2021) Build/RRKS31.Q1-19-17-2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.120 Mobile Safari/537.36",
1227
+ :form_factor => :handheld,
1228
+ :ios? => false,
1229
+ :android? => true,
1230
+ :desktop? => false,
1231
+ :engine => :webkit,
1232
+ :major_engine_version => 537,
1233
+ :os => :android,
1234
+ :os_version => "11",
1235
+ :browser => :chrome,
1236
+ :major_browser_version => 91,
1237
+ :device_vendor => 'Motorola',
1238
+ :device_name => 'moto g power (2021)',
1239
+ },
1240
+ :nintendo_switch => {
1241
+ :user_agent => "Mozilla/5.0 (Nintendo Switch; WifiWebAuthApplet) AppleWebKit/601.6 (KHTML, like Gecko) NF/4.0.0.5.10 NintendoBrowser/5.1.0.13343",
1242
+ :form_factor => :console,
1243
+ :ios? => false,
1244
+ :android? => false,
1245
+ :desktop? => false,
1246
+ :engine => :webkit,
1247
+ :major_engine_version => 601,
1248
+ :device_vendor => 'Nintendo',
1249
+ :device_name => 'Switch',
1250
+ },
1251
+ :amazon_fire_tv => {
1252
+ :user_agent => "Mozilla/5.0 (Linux; Android 9; AFTMM Build/PS7233) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.147 Mobile Safari/537.36",
1253
+ :form_factor => :console,
1254
+ :ios? => false,
1255
+ :android? => true,
1256
+ :desktop? => false,
1257
+ :engine => :webkit,
1258
+ :major_engine_version => 537,
1259
+ :os => :android,
1260
+ :os_version => "9",
1261
+ :browser => :chrome,
1262
+ :major_browser_version => 79,
1263
+ :device_vendor => 'Amazon',
1264
+ :device_name => 'AFTMM',
1265
+ },
1266
+ :amazon_fire_tablet => {
1267
+ :user_agent => "Mozilla/5.0 (Linux; Android 7.1.2; KFKAWI Build/NS6265) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.125 Mobile Safari/537.36",
1268
+ :form_factor => :tablet,
1269
+ :ios? => false,
1270
+ :android? => true,
1271
+ :desktop? => false,
1272
+ :engine => :webkit,
1273
+ :major_engine_version => 537,
1274
+ :os => :android,
1275
+ :os_version => "7.1.2",
1276
+ :browser => :chrome,
1277
+ :major_browser_version => 59,
1278
+ :device_vendor => 'Amazon',
1279
+ :device_name => 'KFKAWI',
1280
+ },
1281
+ :apple_tv => {
1282
+ :user_agent => "AppleTV6,2/11.1 CFNetwork/887.60.2 Darwin/17.3.0",
1283
+ :form_factor => :console,
1284
+ :ios? => false,
1285
+ :android? => false,
1286
+ :desktop? => false,
1287
+ :device_vendor => 'Apple',
1288
+ :device_name => 'Apple TV',
1289
+ },
1290
+ :chromecast => {
1291
+ :user_agent => "Mozilla/5.0 (CrKey armv7l 1.5.16041) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.0 Safari/537.36",
1292
+ :form_factor => :console,
1293
+ :ios? => false,
1294
+ :android? => false,
1295
+ :desktop? => false,
1296
+ :engine => :webkit,
1297
+ :major_engine_version => 537,
1298
+ :browser => :chrome,
1299
+ :major_browser_version => 31,
1300
+ :device_vendor => 'Google',
1301
+ :device_name => 'Chromecast',
1302
+ },
1303
+ :roku_device => {
1304
+ :user_agent => "Roku4640X/DVP-7.70 (297.70E04154A)",
1305
+ :form_factor => :console,
1306
+ :ios? => false,
1307
+ :android? => false,
1308
+ :desktop? => false,
1309
+ :device_vendor => 'Roku',
1310
+ :device_name => '4640X',
1311
+ },
1312
+ :xiaomi_pad_7_pro => {
1313
+ :user_agent => "Dalvik/2.1.0 (Linux; U; Android 15; 24091RPADG Build/AQ3A.240801.002)",
1314
+ :form_factor => :tablet,
1315
+ :ios? => false,
1316
+ :android? => true,
1317
+ :desktop? => false,
1318
+ :os => :android,
1319
+ :os_version => "15",
1320
+ :device_vendor => 'Xiaomi',
1321
+ :device_name => '24091RPADG',
1322
+ },
1323
+ :huawei_pura_70_ultra => {
1324
+ :user_agent => "Mozilla/5.0 (Linux; Android 12; HBP-LX9 Build/HUAWEIHBP-L29; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/130.0.6723.58 Mobile Safari/537.36",
1325
+ :form_factor => :handheld,
1326
+ :ios? => false,
1327
+ :android? => true,
1328
+ :desktop? => false,
1329
+ :engine => :webkit,
1330
+ :major_engine_version => 537,
1331
+ :os => :android,
1332
+ :os_version => "12",
1333
+ :browser => :chrome,
1334
+ :major_browser_version => 130,
1335
+ :device_vendor => 'Huawei',
1336
+ :device_name => 'HBP-LX9',
1337
+ },
1338
+ :windows_10_edge => {
1339
+ :user_agent => "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36 Edg/134.0.0.0",
1340
+ :form_factor => :desktop,
1341
+ :ios? => false,
1342
+ :android? => false,
1343
+ :desktop? => true,
1344
+ :engine => :webkit,
1345
+ :major_engine_version => 537,
1346
+ :os => :windows,
1347
+ :browser => :edge,
1348
+ :major_browser_version => 134,
1349
+ :device_vendor => nil,
1350
+ :device_name => nil,
1351
+ },
1352
+ :xbox_series_x_windows => {
1353
+ :user_agent => "Mozilla/5.0 (Windows NT 10.0; Win64; x64; Xbox; Xbox Series X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.82 Safari/537.36 Edge/20.02",
1354
+ :form_factor => :console,
1355
+ :ios? => false,
1356
+ :android? => false,
1357
+ :desktop? => false,
1358
+ :engine => :webkit,
1359
+ :major_engine_version => 537,
1360
+ :os => :windows,
1361
+ :browser => :edge,
1362
+ :major_browser_version => 20,
1363
+ :device_vendor => 'Microsoft',
1364
+ :device_name => 'Xbox Series X',
1365
+ },
1366
+ :xbox_one_s_windows => {
1367
+ :user_agent => "Mozilla/5.0 (Windows NT 10.0; Win64; x64; XBOX_ONE_ED) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.79 Safari/537.36 Edge/14.14393",
1368
+ :form_factor => :console,
1369
+ :ios? => false,
1370
+ :android? => false,
1371
+ :desktop? => false,
1372
+ :engine => :webkit,
1373
+ :major_engine_version => 537,
1374
+ :os => :windows,
1375
+ :browser => :edge,
1376
+ :major_browser_version => 14,
1377
+ :device_vendor => 'Microsoft',
1378
+ :device_name => 'Xbox One',
1379
+ },
1380
+ :xbox_one_windows_phone => {
1381
+ :user_agent => "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Xbox; Xbox One) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2486.0 Mobile Safari/537.36 Edge/13.10586",
1382
+ :form_factor => :console,
1383
+ :ios? => false,
1384
+ :android? => false,
1385
+ :desktop? => false,
1386
+ :engine => :webkit,
1387
+ :major_engine_version => 537,
1388
+ :os => :windows,
1389
+ :browser => :edge,
1390
+ :major_browser_version => 13,
1391
+ :device_vendor => 'Microsoft',
1392
+ :device_name => 'Xbox One',
1393
+ },
1394
+ :amazon_fire_tv_stick_aftka => {
1395
+ :user_agent => "Mozilla/5.0 (Linux; Android 9; AFTKA) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/70.0.3538.110 Mobile Safari/537.36",
1396
+ :form_factor => :console,
1397
+ :ios? => false,
1398
+ :android? => true,
1399
+ :desktop? => false,
1400
+ :engine => :webkit,
1401
+ :major_engine_version => 537,
1402
+ :os => :android,
1403
+ :os_version => "9",
1404
+ :browser => :chrome,
1405
+ :major_browser_version => 70,
1406
+ :device_vendor => 'Amazon',
1407
+ :device_name => 'AFTKA',
1408
+ },
1409
+ :google_adt_2 => {
1410
+ :user_agent => "Dalvik/2.1.0 (Linux; U; Android 9; ADT-2 Build/PTT5.181126.002)",
1411
+ :form_factor => :console,
1412
+ :ios? => false,
1413
+ :android? => true,
1414
+ :desktop? => false,
1415
+ :os => :android,
1416
+ :os_version => "9",
1417
+ :device_vendor => 'Google',
1418
+ :device_name => 'ADT-2',
1419
+ },
1420
+ :playstation_vita => {
1421
+ :user_agent => "Mozilla/5.0 (PlayStation Vita 3.61) AppleWebKit/537.73 (KHTML, like Gecko) Silk/3.2",
1422
+ :form_factor => :console,
1423
+ :ios? => false,
1424
+ :android? => false,
1425
+ :desktop? => false,
1426
+ :engine => :webkit,
1427
+ :major_engine_version => 537,
1428
+ :device_vendor => 'Sony',
1429
+ :device_name => 'PlayStation Vita',
1430
+ },
1431
+ :onyx_noteair_3c => {
1432
+ :user_agent => "Dalvik/2.1.0 (Linux; U; Android 12; NoteAir3C Build/2023-11-15_15-07_3.5_0a296ec2c)",
1433
+ :form_factor => :tablet,
1434
+ :ios? => false,
1435
+ :android? => true,
1436
+ :desktop? => false,
1437
+ :os => :android,
1438
+ :os_version => "12",
1439
+ :device_vendor => 'Onyx',
1440
+ :device_name => 'NoteAir3C',
1441
+ },
1442
+ :shopify_pos_android_native => {
1443
+ :user_agent => "Mozilla/5.0 (Linux; Android 10; SM-G973U Build/QP1A.190711.020) POS 1.0",
1444
+ :form_factor => :handheld,
1445
+ :ios? => false,
1446
+ :android? => true,
1447
+ :desktop? => false,
1448
+ :os => :android,
1449
+ :os_version => "10",
1450
+ :device_name => 'SM-G973U',
1451
+ },
1452
+ :shopify_pos_android_native_with_locale => {
1453
+ :user_agent => "Mozilla/5.0 (Linux; U; Android 7.1.1; en-US; SM-T560NU Build/NMF26X) POS 2.4.10",
1454
+ :form_factor => :handheld,
1455
+ :ios? => false,
1456
+ :android? => true,
1457
+ :desktop? => false,
1458
+ :os => :android,
1459
+ :os_version => "7.1.1",
1460
+ :device_name => 'SM-T560NU',
1461
+ },
1462
+ :shopify_pos_android_smartwebview => {
1463
+ :user_agent => "Shopify POS/1.0 Mozilla/5.0 (Linux; Android 10; SM-G973U Build/QP1A.190711.020) POS 1.0",
1464
+ :form_factor => :handheld,
1465
+ :ios? => false,
1466
+ :android? => true,
1467
+ :desktop? => false,
1468
+ :os => :android,
1469
+ :os_version => "10",
1470
+ :device_name => 'SM-G973U',
1471
+ },
1472
+ :shopify_pos_android_smartwebview_with_locale => {
1473
+ :user_agent => "Shopify POS/2.4.10 Mozilla/5.0 (Linux; U; Android 7.1.1; en-US; SM-T560NU Build/NMF26X) POS 2.4.10",
1474
+ :form_factor => :handheld,
1475
+ :ios? => false,
1476
+ :android? => true,
1477
+ :desktop? => false,
1478
+ :os => :android,
1479
+ :os_version => "7.1.1",
1480
+ :device_name => 'SM-T560NU',
1481
+ },
678
1482
  }
679
1483
 
1484
+ def test_nil_user_agent_is_preserved
1485
+ assert_nil BrowserSniffer.new(nil).user_agent
1486
+ end
1487
+
1488
+ def test_user_agent_length_is_capped
1489
+ long_user_agent = "a" * 10_000
1490
+ sniffer = BrowserSniffer.new(long_user_agent)
1491
+ assert_equal BrowserSniffer::MAX_USER_AGENT_LENGTH, sniffer.user_agent.length
1492
+ end
1493
+
1494
+ def test_pos_android_regex_does_not_backtrack_catastrophically
1495
+ malicious_user_agent = ("(Android 1.0; a Build/" * 160) + "!"
1496
+ time = Benchmark.realtime { BrowserSniffer.new(malicious_user_agent).form_factor }
1497
+ assert time < 0.1, "Expected POS Android regex to reject crafted UA quickly, took #{time}s"
1498
+ end
1499
+
1500
+ def test_ios_os_regex_does_not_backtrack_catastrophically
1501
+ malicious_user_agent = "iphone os " + ("a" * 320) + "!"
1502
+ time = Benchmark.realtime { BrowserSniffer.new(malicious_user_agent).os }
1503
+ assert time < 0.1, "Expected iOS OS regex to reject crafted UA quickly, took #{time}s"
1504
+ end
1505
+
680
1506
  AGENTS.each do |agent, attributes|
681
1507
  define_method "test_sniff_#{agent}_correctly" do
682
1508
  sniffer = BrowserSniffer.new(attributes[:user_agent])
@@ -827,6 +827,26 @@ describe "Shopify agents" do
827
827
  }), sniffer.os_info
828
828
  end
829
829
 
830
+ it "Shopify CLI can be sniffed" do
831
+ user_agent = "Shopify CLI; v=3.59.3"
832
+ sniffer = BrowserSniffer.new(user_agent)
833
+
834
+ assert_equal ({
835
+ name: 'Shopify CLI',
836
+ version: '3.59.3',
837
+ }), sniffer.browser_info
838
+ end
839
+
840
+ it "Shopify CLI without version can be sniffed" do
841
+ user_agent = "Shopify CLI"
842
+ sniffer = BrowserSniffer.new(user_agent)
843
+
844
+ assert_equal ({
845
+ name: 'Shopify CLI',
846
+ version: nil,
847
+ }), sniffer.browser_info
848
+ end
849
+
830
850
  INCOMPATIBLE_USER_AGENTS = {
831
851
  'missing' => nil,
832
852
 
data/test/test_helper.rb CHANGED
@@ -1,4 +1,6 @@
1
- require 'rubygems'
1
+ $VERBOSE = true
2
+
3
+ require 'benchmark'
2
4
  require 'minitest/autorun'
3
5
 
4
6
  require "#{File.dirname(__FILE__)}/../init"
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: browser_sniffer
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.4.0
4
+ version: 2.5.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shopify
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2024-01-12 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: bundler
@@ -59,10 +58,13 @@ executables: []
59
58
  extensions: []
60
59
  extra_rdoc_files: []
61
60
  files:
61
+ - ".github/dependabot.yml"
62
62
  - ".github/workflows/cla.yml"
63
63
  - ".github/workflows/test.yml"
64
64
  - ".gitignore"
65
+ - ".ruby-version"
65
66
  - Gemfile
67
+ - Gemfile.lock
66
68
  - LICENSE.txt
67
69
  - README.md
68
70
  - Rakefile
@@ -82,7 +84,6 @@ licenses:
82
84
  - MIT
83
85
  metadata:
84
86
  allowed_push_host: https://rubygems.org
85
- post_install_message:
86
87
  rdoc_options: []
87
88
  require_paths:
88
89
  - lib
@@ -97,8 +98,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
97
98
  - !ruby/object:Gem::Version
98
99
  version: '0'
99
100
  requirements: []
100
- rubygems_version: 3.5.4
101
- signing_key:
101
+ rubygems_version: 4.0.21
102
102
  specification_version: 4
103
103
  summary: Parses user agent strings and boils it all down to a few simple classifications.
104
104
  test_files: []