browserx 1.0.0

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 (112) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +29 -0
  3. data/LICENSE.md +21 -0
  4. data/README.md +438 -0
  5. data/bot_exceptions.yml +5 -0
  6. data/bots.yml +327 -0
  7. data/languages.yml +113 -0
  8. data/lib/browser/accept_language.rb +55 -0
  9. data/lib/browser/action_controller.rb +20 -0
  10. data/lib/browser/aliases.rb +33 -0
  11. data/lib/browser/alipay.rb +21 -0
  12. data/lib/browser/base.rb +292 -0
  13. data/lib/browser/blackberry.rb +23 -0
  14. data/lib/browser/bot/empty_user_agent_matcher.rb +11 -0
  15. data/lib/browser/bot/keyword_matcher.rb +11 -0
  16. data/lib/browser/bot/known_bots_matcher.rb +11 -0
  17. data/lib/browser/bot.rb +119 -0
  18. data/lib/browser/browser.rb +116 -0
  19. data/lib/browser/chrome.rb +43 -0
  20. data/lib/browser/detect_version.rb +22 -0
  21. data/lib/browser/device/android.rb +20 -0
  22. data/lib/browser/device/base.rb +13 -0
  23. data/lib/browser/device/blackberry_playbook.rb +19 -0
  24. data/lib/browser/device/ipad.rb +19 -0
  25. data/lib/browser/device/iphone.rb +19 -0
  26. data/lib/browser/device/ipod_touch.rb +19 -0
  27. data/lib/browser/device/kindle.rb +19 -0
  28. data/lib/browser/device/kindle_fire.rb +19 -0
  29. data/lib/browser/device/playstation3.rb +19 -0
  30. data/lib/browser/device/playstation4.rb +19 -0
  31. data/lib/browser/device/psp.rb +19 -0
  32. data/lib/browser/device/psvita.rb +19 -0
  33. data/lib/browser/device/samsung.rb +33 -0
  34. data/lib/browser/device/surface.rb +23 -0
  35. data/lib/browser/device/switch.rb +19 -0
  36. data/lib/browser/device/tv.rb +19 -0
  37. data/lib/browser/device/unknown.rb +19 -0
  38. data/lib/browser/device/wii.rb +19 -0
  39. data/lib/browser/device/wiiu.rb +19 -0
  40. data/lib/browser/device/xbox_360.rb +19 -0
  41. data/lib/browser/device/xbox_one.rb +19 -0
  42. data/lib/browser/device.rb +222 -0
  43. data/lib/browser/duck_duck_go.rb +22 -0
  44. data/lib/browser/edge.rb +25 -0
  45. data/lib/browser/electron.rb +22 -0
  46. data/lib/browser/epiphany.rb +21 -0
  47. data/lib/browser/facebook.rb +23 -0
  48. data/lib/browser/firefox.rb +21 -0
  49. data/lib/browser/google_search_app.rb +21 -0
  50. data/lib/browser/huawei_browser.rb +21 -0
  51. data/lib/browser/instagram.rb +21 -0
  52. data/lib/browser/internet_explorer.rb +63 -0
  53. data/lib/browser/maxthon.rb +21 -0
  54. data/lib/browser/meta/base.rb +42 -0
  55. data/lib/browser/meta/device.rb +11 -0
  56. data/lib/browser/meta/generic_browser.rb +15 -0
  57. data/lib/browser/meta/id.rb +11 -0
  58. data/lib/browser/meta/ie.rb +20 -0
  59. data/lib/browser/meta/ios.rb +11 -0
  60. data/lib/browser/meta/mobile.rb +11 -0
  61. data/lib/browser/meta/platform.rb +11 -0
  62. data/lib/browser/meta/proxy.rb +11 -0
  63. data/lib/browser/meta/safari.rb +11 -0
  64. data/lib/browser/meta/tablet.rb +11 -0
  65. data/lib/browser/meta/webkit.rb +11 -0
  66. data/lib/browser/meta.rb +14 -0
  67. data/lib/browser/micro_messenger.rb +21 -0
  68. data/lib/browser/middleware/context/additions.rb +18 -0
  69. data/lib/browser/middleware/context/url_methods.rb +15 -0
  70. data/lib/browser/middleware/context.rb +22 -0
  71. data/lib/browser/middleware.rb +70 -0
  72. data/lib/browser/miui_browser.rb +21 -0
  73. data/lib/browser/nokia.rb +21 -0
  74. data/lib/browser/opera.rb +21 -0
  75. data/lib/browser/otter.rb +21 -0
  76. data/lib/browser/phantom_js.rb +21 -0
  77. data/lib/browser/platform/adobe_air.rb +23 -0
  78. data/lib/browser/platform/android.rb +23 -0
  79. data/lib/browser/platform/base.rb +18 -0
  80. data/lib/browser/platform/blackberry.rb +23 -0
  81. data/lib/browser/platform/chrome_os.rb +23 -0
  82. data/lib/browser/platform/firefox_os.rb +23 -0
  83. data/lib/browser/platform/ios.rb +36 -0
  84. data/lib/browser/platform/kai_os.rb +23 -0
  85. data/lib/browser/platform/linux.rb +23 -0
  86. data/lib/browser/platform/mac.rb +25 -0
  87. data/lib/browser/platform/unknown.rb +23 -0
  88. data/lib/browser/platform/windows.rb +23 -0
  89. data/lib/browser/platform/windows_mobile.rb +23 -0
  90. data/lib/browser/platform/windows_phone.rb +23 -0
  91. data/lib/browser/platform.rb +193 -0
  92. data/lib/browser/qq.rb +25 -0
  93. data/lib/browser/rails.rb +23 -0
  94. data/lib/browser/safari.rb +40 -0
  95. data/lib/browser/samsung_browser.rb +21 -0
  96. data/lib/browser/snapchat.rb +21 -0
  97. data/lib/browser/sougou_browser.rb +24 -0
  98. data/lib/browser/sputnik.rb +21 -0
  99. data/lib/browser/testing.rb +27 -0
  100. data/lib/browser/uc_browser.rb +21 -0
  101. data/lib/browser/unknown.rb +32 -0
  102. data/lib/browser/version.rb +5 -0
  103. data/lib/browser/weibo.rb +21 -0
  104. data/lib/browser/yandex.rb +21 -0
  105. data/lib/browser.rb +4 -0
  106. data/lib/browserx.rb +3 -0
  107. data/samsung.yml +138 -0
  108. data/search_engines.yml +7 -0
  109. data/test/ua.yml +181 -0
  110. data/test/ua_bots.yml +121 -0
  111. data/test/ua_search_engines.yml +7 -0
  112. metadata +267 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: b8b37f466da96715f58be6f7581204d5a46fe12ed3ea96995b46cbcc48a95e1f
4
+ data.tar.gz: ff7419bb6d7162a3abcf82dfac7e851dd50d55605dbea351eac97b11d819cacb
5
+ SHA512:
6
+ metadata.gz: be7daa1b157872be556fe319352180b12f978f42aedf23e9e43eb880905e4d6d849cf7ea268281ca86cf56d3baf46707f4aafd785ab0c589a5fdef57d99e650f
7
+ data.tar.gz: 19d21da9ee4eb84763cb4609c2b160f9ac71f6656a783ec4d31d87c2558ee71a7d27e99f4e5437119998dcc2f13c2c00abb8ce478e8d8314b1012c76773cc1fd
data/CHANGELOG.md ADDED
@@ -0,0 +1,29 @@
1
+ # Changelog
2
+
3
+ ## [Unreleased]
4
+
5
+ ## [1.0.0] — 2026-11-04
6
+
7
+ First release as `browserx`, forked from browser 6.2.0 (`dd88933`). Same API: `gem "browserx"` replaces
8
+ `gem "browser"` without touching a call site. Earlier versions are browser's: see its
9
+ [changelog](https://github.com/fnando/browser/blob/main/CHANGELOG.md).
10
+
11
+ **Breaking**
12
+ - Requires Ruby 3.3 or later: 3.2 reached its end of life on 2026-03-31.
13
+ - Tested against Rails 8.0 and 8.1 only: 7.0, 7.1 and 7.2 reached their end of life.
14
+
15
+ **Faster**
16
+ - Bots are detected through one prefix-trie regexp instead of one match per entry, and without detecting the
17
+ browser twice; platform and device matchers are built lazily. A request takes 45% less time and half the
18
+ allocations.
19
+
20
+ **Fixed**
21
+ - The railtie loads Rails, not only itself, so it works without a preloaded ActiveSupport.
22
+ - `lib/browserx.rb` exists, so Bundler's auto-require loads the gem.
23
+
24
+ **New**
25
+ - A corpus of 20,328 user agents, with snapshot and diff scripts proving answers match upstream, and a
26
+ request benchmark.
27
+
28
+ [Unreleased]: https://github.com/next-gen-gems/browserx/compare/v1.0.0...HEAD
29
+ [1.0.0]: https://github.com/next-gen-gems/browserx/releases/tag/v1.0.0
data/LICENSE.md ADDED
@@ -0,0 +1,21 @@
1
+ # The MIT License (MIT)
2
+
3
+ Copyright (c) 2010 Nando Vieira
4
+ Copyright (c) 2026 Level UP Solutions
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
7
+ this software and associated documentation files (the "Software"), to deal in
8
+ the Software without restriction, including without limitation the rights to
9
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
10
+ the Software, and to permit persons to whom the Software is furnished to do so,
11
+ subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
18
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
19
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
20
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
21
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,438 @@
1
+ # BrowserX
2
+
3
+ Browser detection for Ruby, with ActionController integration. BrowserX is a drop-in replacement for the
4
+ [browser](https://rubygems.org/gems/browser) gem: put `gem "browserx"` in your Gemfile instead of
5
+ `gem "browser"`, and every call site keeps working — same `Browser` namespace, same `require "browser"`, same
6
+ predicates. It answers nearly twice as fast (61 µs and 129 objects per request instead of 110 µs and 245 on
7
+ Ruby 4.0 with YJIT), and its bot list is meant to be kept fresh from permissively licensed sources
8
+ ([crawler-user-agents](https://github.com/monperrus/crawler-user-agents),
9
+ [ai.robots.txt](https://github.com/ai-robots-txt/ai.robots.txt)).
10
+
11
+ Site: https://next-gen-gems.github.io/browserx/
12
+
13
+ ## Installation
14
+
15
+ ```ruby
16
+ gem "browserx"
17
+ ```
18
+
19
+ ## Usage
20
+
21
+ ```ruby
22
+ require "browser"
23
+
24
+ browser = Browser.new("Some User Agent", accept_language: "en-us")
25
+
26
+ # General info
27
+ browser.bot?
28
+ browser.chrome?
29
+ browser.chromium_based?
30
+ browser.core_media?
31
+ browser.duck_duck_go?
32
+ browser.edge? # Newest MS browser
33
+ browser.electron? # Electron Framework
34
+ browser.firefox?
35
+ browser.full_version
36
+ browser.ie?
37
+ browser.ie?(6) # detect specific IE version
38
+ browser.ie?([">8", "<10"]) # detect specific IE (IE9).
39
+ browser.known? # has the browser been successfully detected?
40
+ browser.unknown? # the browser wasn't detected.
41
+ browser.meta # an array with several attributes
42
+ browser.name # readable browser name
43
+ browser.nokia?
44
+ browser.opera?
45
+ browser.opera_mini?
46
+ browser.phantom_js?
47
+ browser.quicktime?
48
+ browser.safari?
49
+ browser.safari_webapp_mode?
50
+ browser.samsung_browser?
51
+ browser.to_s # the meta info joined by space
52
+ browser.uc_browser?
53
+ browser.version # major version number
54
+ browser.webkit?
55
+ browser.webkit_full_version
56
+ browser.yandex?
57
+ browser.wechat?
58
+ browser.qq?
59
+ browser.weibo?
60
+ browser.sputnik?
61
+ browser.sougou_browser?
62
+ browser.epiphany?
63
+
64
+ # Get bot info
65
+ browser.bot.name
66
+ browser.bot.search_engine?
67
+ browser.bot?
68
+ browser.bot.why? # shows which matcher detected this user agent as a bot.
69
+ Browser::Bot.why?(ua)
70
+
71
+ # Get device info
72
+ browser.device
73
+ browser.device.id
74
+ browser.device.name
75
+ browser.device.unknown?
76
+ browser.device.blackberry_playbook?
77
+ browser.device.console?
78
+ browser.device.ipad?
79
+ browser.device.iphone?
80
+ browser.device.ipod_touch?
81
+ browser.device.kindle?
82
+ browser.device.kindle_fire?
83
+ browser.device.mobile?
84
+ browser.device.nintendo?
85
+ browser.device.playstation?
86
+ browser.device.ps3?
87
+ browser.device.ps4?
88
+ browser.device.psp?
89
+ browser.device.silk?
90
+ browser.device.surface?
91
+ browser.device.tablet?
92
+ browser.device.tv?
93
+ browser.device.vita?
94
+ browser.device.wii?
95
+ browser.device.wiiu?
96
+ browser.device.samsung?
97
+ browser.device.switch?
98
+ browser.device.xbox?
99
+ browser.device.xbox_360?
100
+ browser.device.xbox_one?
101
+
102
+ # Get platform info
103
+ browser.platform
104
+ browser.platform.id
105
+ browser.platform.name
106
+ browser.platform.version # e.g. 9 (for iOS9)
107
+ browser.platform.adobe_air?
108
+ browser.platform.android?
109
+ browser.platform.android?(4.2) # detect Android Jelly Bean 4.2
110
+ browser.platform.android_app? # detect webview in an Android app
111
+ browser.platform.android_webview? # alias for android_app?
112
+ browser.platform.blackberry?
113
+ browser.platform.blackberry?(10) # detect specific BlackBerry version
114
+ browser.platform.chrome_os?
115
+ browser.platform.firefox_os?
116
+ browser.platform.ios? # detect iOS
117
+ browser.platform.ios?(9) # detect specific iOS version
118
+ browser.platform.ios_app? # detect webview in an iOS app
119
+ browser.platform.ios_webview? # alias for ios_app?
120
+ browser.platform.linux?
121
+ browser.platform.mac?
122
+ browser.platform.unknown?
123
+ browser.platform.windows10?
124
+ browser.platform.windows7?
125
+ browser.platform.windows8?
126
+ browser.platform.windows8_1?
127
+ browser.platform.windows?
128
+ browser.platform.windows_mobile?
129
+ browser.platform.windows_phone?
130
+ browser.platform.windows_rt?
131
+ browser.platform.windows_touchscreen_desktop?
132
+ browser.platform.windows_vista?
133
+ browser.platform.windows_wow64?
134
+ browser.platform.windows_x64?
135
+ browser.platform.windows_x64_inclusive?
136
+ browser.platform.windows_xp?
137
+ browser.platform.kai_os?
138
+ ```
139
+
140
+ ### Aliases
141
+
142
+ To add aliases like `mobile?` and `tablet?` to the base object (e.g
143
+ `browser.mobile?`), require the `browser/aliases` file and extend the
144
+ Browser::Base object like the following:
145
+
146
+ ```ruby
147
+ require "browser/aliases"
148
+ Browser::Base.include(Browser::Aliases)
149
+
150
+ browser = Browser.new("Some user agent")
151
+ browser.mobile? #=> false
152
+ ```
153
+
154
+ ### What's being detected?
155
+
156
+ - For a list of platform detections, check
157
+ [lib/browser/platform.rb](lib/browser/platform.rb)
158
+ - For a list of device detections, check
159
+ [lib/browser/device.rb](lib/browser/device.rb)
160
+ - For a list of bot detections, check
161
+ [bots.yml](bots.yml)
162
+
163
+ ### Detecting modern browsers
164
+
165
+ To detect whether a browser can be considered as modern or not, create a method
166
+ that abstracts your versioning constraints. The following example will consider
167
+ any of the following browsers as a modern:
168
+
169
+ ```ruby
170
+ # Expects an Browser instance,
171
+ # like in `Browser.new(user_agent, accept_language: language)`.
172
+ def modern_browser?(browser)
173
+ [
174
+ browser.chrome?(">= 65"),
175
+ browser.safari?(">= 10"),
176
+ browser.firefox?(">= 52"),
177
+ browser.ie?(">= 11") && !browser.compatibility_view?,
178
+ browser.edge?(">= 15"),
179
+ browser.opera?(">= 50"),
180
+ browser.facebook?
181
+ && browser.safari_webapp_mode?
182
+ && browser.webkit_full_version.to_i >= 602
183
+ ].any?
184
+ end
185
+ ```
186
+
187
+ ### Migrating from browser
188
+
189
+ Replace `gem "browser"` with `gem "browserx"` in the Gemfile, nothing else: `require "browser"`, the `Browser`
190
+ namespace and every predicate stay. To roll back, put `gem "browser"` back. BrowserX requires Ruby 3.3 or later
191
+ and is tested on Rails 8.0 and 8.1.
192
+
193
+ ### Rails integration
194
+
195
+ Just add it to the Gemfile.
196
+
197
+ ```ruby
198
+ gem "browserx"
199
+ ```
200
+
201
+ This adds a helper method called `browser`, that inspects your current user
202
+ agent.
203
+
204
+ ```erb
205
+ <% if browser.ie?(6) %>
206
+ <p class="disclaimer">You're running an older IE version. Please update it!</p>
207
+ <% end %>
208
+ ```
209
+
210
+ If you want to use Browser on your Rails app but don't want to taint your
211
+ controller, use the following line on your Gemfile:
212
+
213
+ ```ruby
214
+ gem "browserx", require: "browser/browser"
215
+ ```
216
+
217
+ ### Accept Language
218
+
219
+ Parses the accept-language header from an HTTP request and produces an array of
220
+ language objects sorted by quality.
221
+
222
+ ```ruby
223
+ browser = Browser.new("Some User Agent", accept_language: "en-us")
224
+
225
+ browser.accept_language.class
226
+ #=> Array
227
+
228
+ language = browser.accept_language.first
229
+
230
+ language.code
231
+ #=> "en"
232
+
233
+ language.region
234
+ #=> "US"
235
+
236
+ language.full
237
+ #=> "en-US"
238
+
239
+ language.quality
240
+ #=> 1.0
241
+
242
+ language.name
243
+ #=> "English/United States"
244
+ ```
245
+
246
+ Result is always sorted in quality order from highest to lowest. As per the HTTP
247
+ spec:
248
+
249
+ - omitting the quality value implies 1.0.
250
+ - quality value equal to zero means that is not accepted by the client.
251
+
252
+ ### Internet Explorer
253
+
254
+ Internet Explorer has a compatibility view mode that allows newer versions
255
+ (IE8+) to run as an older version. Browser will always return the navigator
256
+ version, ignoring the compatibility view version, when defined. If you need to
257
+ get the engine's version, you have to use `Browser#msie_version` and
258
+ `Browser#msie_full_version`.
259
+
260
+ So, let's say an user activates compatibility view in a IE11 browser. This is
261
+ what you'll get:
262
+
263
+ ```ruby
264
+ browser.version
265
+ #=> 11
266
+
267
+ browser.full_version
268
+ #=> 11.0
269
+
270
+ browser.msie_version
271
+ #=> 7
272
+
273
+ browser.msie_full_version
274
+ #=> 7.0
275
+
276
+ browser.compatibility_view?
277
+ #=> true
278
+ ```
279
+
280
+ This behavior changed in browser v1.0.0; previously there wasn't a way of getting the
281
+ real browser version.
282
+
283
+ ### Safari
284
+
285
+ iOS webviews and web apps aren't detected as Safari anymore, so be aware of that
286
+ if that's your case. You can use a combination of platform and webkit detection
287
+ to do whatever you want.
288
+
289
+ ```ruby
290
+ # iPad's Safari running as web app mode.
291
+ browser = Browser.new("Mozilla/5.0 (iPad; U; CPU OS 3_2_1 like Mac OS X; en-us) AppleWebKit/531.21.10 (KHTML, like Gecko) Mobile/7B405")
292
+
293
+ browser.safari?
294
+ #=> false
295
+
296
+ browser.webkit?
297
+ #=> true
298
+
299
+ browser.platform.ios?
300
+ #=> true
301
+ ```
302
+
303
+ ### Bots
304
+
305
+ The bot detection is quite aggressive. Anything that matches at least one of the
306
+ following requirements will be considered a bot.
307
+
308
+ - Empty user agent string
309
+ - User agent that matches `/crawl|fetch|search|monitoring|spider|bot/`
310
+ - Any known bot listed under
311
+ [bots.yml](bots.yml)
312
+
313
+ To add custom matchers, you can add a callable object to
314
+ `Browser::Bot.matchers`. The following example matches everything that has a
315
+ `externalhit` substring on it. The bot name will always be `General Bot`.
316
+
317
+ ```ruby
318
+ Browser::Bot.matchers << ->(ua, _browser) { ua.match?(/externalhit/i) }
319
+ ```
320
+
321
+ To clear all matchers, including the ones that are bundled, use
322
+ `Browser::Bot.matchers.clear`. You can re-add built-in matchers by doing the
323
+ following:
324
+
325
+ ```ruby
326
+ Browser::Bot.matchers += Browser::Bot.default_matchers
327
+ ```
328
+
329
+ To restore v2's bot detection, remove the following matchers:
330
+
331
+ ```ruby
332
+ Browser::Bot.matchers.delete(Browser::Bot::KeywordMatcher)
333
+ Browser::Bot.matchers.delete(Browser::Bot::EmptyUserAgentMatcher)
334
+ ```
335
+
336
+ To extend the bot list, you can manipulate the methods below:
337
+
338
+ ```ruby
339
+ Browser::Bot.bots.merge!(new_bots_hash)
340
+ Browser::Bot.bot_exceptions += new_exceptions
341
+ Browser::Bot.search_engines.merge!(new_search_engines_hash)
342
+ ```
343
+
344
+ ### Middleware
345
+
346
+ You can use the `Browser::Middleware` to redirect user agents.
347
+
348
+ ```ruby
349
+ use Browser::Middleware do
350
+ redirect_to "/upgrade" if browser.ie?
351
+ end
352
+ ```
353
+
354
+ If you're using Rails, you can use the route helper methods. Just add something
355
+ like the following to a initializer file (`config/initializers/browser.rb`).
356
+
357
+ ```ruby
358
+ Rails.configuration.middleware.use Browser::Middleware do
359
+ redirect_to upgrade_path if browser.ie?
360
+ end
361
+ ```
362
+
363
+ If you need access to the `Rack::Request` object (e.g. to exclude a path), you
364
+ can do so with `request`.
365
+
366
+ ```ruby
367
+ Rails.configuration.middleware.use Browser::Middleware do
368
+ redirect_to upgrade_path if browser.ie? && request.env["PATH_INFO"] != "/exclude_me"
369
+ end
370
+ ```
371
+
372
+ ### Restrictions
373
+
374
+ - User agent has a size limit of 2048 bytes. This can be customized through
375
+ `Browser.user_agent_size_limit=(size)`.
376
+ - Accept-Language has a size limit of 2048 bytes. This can be customized through
377
+ `Browser.accept_language_size_limit=(size)`.
378
+
379
+ If size is not respected, then `Browser::Error` is raised.
380
+
381
+ ```ruby
382
+ Browser.user_agent_size_limit = 4096
383
+ Browser.accept_language_size_limit = 4096
384
+ ```
385
+
386
+ ## Performance
387
+
388
+ `bench/request.rb` replays what a web request asks (name, version, `bot?`, platform, device, accept-language) over
389
+ 2,000 distinct user agents of the corpus and over 20 repeated ones, median of 7. On Ruby 4.0.9 with YJIT, browser
390
+ 6.2.0 takes 110 µs and 245 objects per request, BrowserX 1.0.0 61 µs and 129 objects.
391
+
392
+ ```bash
393
+ ruby --yjit -I path/to/upstream/lib bench/request.rb
394
+ ruby --yjit -I lib bench/request.rb
395
+ ```
396
+
397
+ ## Development
398
+
399
+ ```bash
400
+ bundle install
401
+ bundle exec rake test
402
+ bundle exec rubocop
403
+ ```
404
+
405
+ A change that is not meant to change an answer proves it on the corpus of 20,000 user agents, against a plain
406
+ clone of [fnando/browser](https://github.com/fnando/browser) at `dd88933`:
407
+
408
+ ```bash
409
+ ruby -I path/to/upstream/lib script/corpus_snapshot.rb > /tmp/upstream.jsonl
410
+ ruby -I lib script/corpus_snapshot.rb > /tmp/browserx.jsonl
411
+ ruby script/corpus_diff.rb /tmp/upstream.jsonl /tmp/browserx.jsonl
412
+ ```
413
+
414
+ To add a bot, add its user agent to `test/ua_bots.yml` and its readable name to `bots.yml`, keyed by a
415
+ lowercased string the user agent contains. A search engine goes in `test/ua_search_engines.yml`,
416
+ `test/ua_bots.yml` and `search_engines.yml`.
417
+
418
+ ### Releasing
419
+
420
+ Changes go under `## [Unreleased]` in `CHANGELOG.md` as they land. To release them, from an up-to-date `main`
421
+ whose CI is green:
422
+
423
+ ```sh
424
+ bin/release minor --dry-run # the notes and the changes, nothing written
425
+ bin/release minor # or patch, major, or an exact version such as 1.2.0
426
+ ```
427
+
428
+ `bin/release` dates the Unreleased section, bumps the version, commits, and pushes the commit with a signed tag
429
+ `vX.Y.Z`. The tag starts the Release workflow, which publishes the gem to RubyGems through trusted publishing and
430
+ creates the GitHub release with the version's notes and the `.gem`.
431
+
432
+ ## License
433
+
434
+ MIT, by Level UP Solutions, see [LICENSE.md](LICENSE.md). Originally written by Nando Vieira as
435
+ [fnando/browser](https://github.com/fnando/browser) (MIT). The corpus gathers user agents from
436
+ [ua-parser/uap-core](https://github.com/ua-parser/uap-core) (Apache 2.0),
437
+ [crawler-user-agents](https://github.com/monperrus/crawler-user-agents) (MIT) and browser's test fixtures: see
438
+ [corpus/SOURCES.md](corpus/SOURCES.md).
@@ -0,0 +1,5 @@
1
+ ---
2
+ - ruby build
3
+ - pinterest/android
4
+ - pinterest/ios
5
+ - yandexsearchbrowser