platform_agent 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.
- checksums.yaml +7 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +40 -0
- data/MIT-LICENSE +20 -0
- data/README.md +54 -0
- data/Rakefile +11 -0
- data/lib/platform_agent.rb +119 -0
- data/platform_agent.gemspec +20 -0
- data/test/platform_agent_test.rb +91 -0
- metadata +108 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 37665e6536aba2833b93dc0f111046ecd115a57c49e914301e2cf81921e7ae18
|
4
|
+
data.tar.gz: a3601b3632b89ea98e8b3e5a1f8ea10b325de7c566811aa4b93a6ae5e59dbf2f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 62f9130310cc151af360e5720f53eb5319c156742e6b61c4ee1dc12c80e05c9e4e33c254cb07f48f72ffd69f8dbc763ee4798d98cb726f5c8b35c84300b4867d
|
7
|
+
data.tar.gz: 979a7288c0d77cdc4af9b121b32a188b3b0cb737bc498f6c4f20b1d5fb7fabfd137e34687a2077ed3d85318d0ee32ae0e26478b13d441b6279bd6eac9c145d03
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
platform_agent (1.0.0)
|
5
|
+
activesupport (>= 5.2.0)
|
6
|
+
useragent (~> 0.16.3)
|
7
|
+
|
8
|
+
GEM
|
9
|
+
remote: https://rubygems.org/
|
10
|
+
specs:
|
11
|
+
activemodel (5.2.1)
|
12
|
+
activesupport (= 5.2.1)
|
13
|
+
activesupport (5.2.1)
|
14
|
+
concurrent-ruby (~> 1.0, >= 1.0.2)
|
15
|
+
i18n (>= 0.7, < 2)
|
16
|
+
minitest (~> 5.1)
|
17
|
+
tzinfo (~> 1.1)
|
18
|
+
byebug (10.0.2)
|
19
|
+
concurrent-ruby (1.0.5)
|
20
|
+
i18n (1.1.0)
|
21
|
+
concurrent-ruby (~> 1.0)
|
22
|
+
minitest (5.11.3)
|
23
|
+
rake (12.3.1)
|
24
|
+
thread_safe (0.3.6)
|
25
|
+
tzinfo (1.2.5)
|
26
|
+
thread_safe (~> 0.1)
|
27
|
+
useragent (0.16.10)
|
28
|
+
|
29
|
+
PLATFORMS
|
30
|
+
ruby
|
31
|
+
|
32
|
+
DEPENDENCIES
|
33
|
+
activemodel (>= 5.2.0)
|
34
|
+
bundler (~> 1.15)
|
35
|
+
byebug
|
36
|
+
platform_agent!
|
37
|
+
rake
|
38
|
+
|
39
|
+
BUNDLED WITH
|
40
|
+
1.17.1
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2019 Basecamp
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
# Platform Agent
|
2
|
+
|
3
|
+
Parse user agent to discern the common platforms that UIs are tailored for. Expected that you'll inherit from this to specify native apps.
|
4
|
+
|
5
|
+
## Examples
|
6
|
+
|
7
|
+
```ruby
|
8
|
+
class ApplicationPlatform < PlatformAgent
|
9
|
+
def ios_app?
|
10
|
+
match? /BC3 iOS/
|
11
|
+
end
|
12
|
+
|
13
|
+
def android_app?
|
14
|
+
match? /BC3 Android/
|
15
|
+
end
|
16
|
+
|
17
|
+
def mac_app?
|
18
|
+
match?(/Electron/) && match?(/basecamp3/) && match?(/Macintosh/)
|
19
|
+
end
|
20
|
+
|
21
|
+
def windows_app?
|
22
|
+
match?(/Electron/) && match?(/basecamp3/) && match?(/Windows/)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
module SetPlatform
|
27
|
+
extend ActiveSupport::Concern
|
28
|
+
|
29
|
+
included do
|
30
|
+
helper_method :platform
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
def platform
|
35
|
+
@platform ||= ApplicationPlatform.new(request.user_agent)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class ApplicationController < ActionController::Base
|
40
|
+
include SetPlatform
|
41
|
+
end
|
42
|
+
|
43
|
+
<% if platform.phone? %>
|
44
|
+
Do phone specific stuff!
|
45
|
+
<% end %>
|
46
|
+
```
|
47
|
+
|
48
|
+
## Maintenance Expectations
|
49
|
+
|
50
|
+
This library is an extraction from Basecamp that's been sufficient in almost unaltered form for years. While contributions are always welcome, do not expect a lot of feature evolution beyond the basics.
|
51
|
+
|
52
|
+
## License
|
53
|
+
|
54
|
+
Platform Agent is released under the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,119 @@
|
|
1
|
+
require "user_agent"
|
2
|
+
|
3
|
+
class PlatformAgent
|
4
|
+
def initialize(user_agent_string)
|
5
|
+
self.user_agent_string = user_agent_string
|
6
|
+
end
|
7
|
+
|
8
|
+
delegate :browser, :version, :product, to: :user_agent
|
9
|
+
|
10
|
+
def desktop?
|
11
|
+
!mobile?
|
12
|
+
end
|
13
|
+
|
14
|
+
def mobile?
|
15
|
+
phone? || tablet? || mobile_app?
|
16
|
+
end
|
17
|
+
|
18
|
+
def phone?
|
19
|
+
iphone? || android? || other_phones?
|
20
|
+
end
|
21
|
+
|
22
|
+
def iphone?
|
23
|
+
match? /iPhone/
|
24
|
+
end
|
25
|
+
|
26
|
+
def android_phone?
|
27
|
+
!android_app? && android?
|
28
|
+
end
|
29
|
+
|
30
|
+
def other_phones?
|
31
|
+
match? /(iPod|Windows Phone|BlackBerry|BB10.*Mobile|Mobile.*Firefox)/
|
32
|
+
end
|
33
|
+
|
34
|
+
def tablet?
|
35
|
+
ipad? || match?(/(Kindle|Silk)/)
|
36
|
+
end
|
37
|
+
|
38
|
+
def ipad?
|
39
|
+
match? /iPad/
|
40
|
+
end
|
41
|
+
|
42
|
+
def android?
|
43
|
+
match? /Android/
|
44
|
+
end
|
45
|
+
|
46
|
+
def native_app?
|
47
|
+
mobile_app? || desktop_app?
|
48
|
+
end
|
49
|
+
|
50
|
+
def mobile_app?
|
51
|
+
ios_app? || android_app?
|
52
|
+
end
|
53
|
+
|
54
|
+
def iphone_app?
|
55
|
+
iphone? && ios_app?
|
56
|
+
end
|
57
|
+
|
58
|
+
def ipad_app?
|
59
|
+
ipad? && ios_app?
|
60
|
+
end
|
61
|
+
|
62
|
+
# Must overwrite with app-specific match
|
63
|
+
def ios_app?
|
64
|
+
false
|
65
|
+
end
|
66
|
+
|
67
|
+
# Must overwrite with app-specific match
|
68
|
+
def android_app?
|
69
|
+
false
|
70
|
+
end
|
71
|
+
|
72
|
+
def desktop_app?
|
73
|
+
mac_app? || windows_app?
|
74
|
+
end
|
75
|
+
|
76
|
+
# Must overwrite with app-specific match
|
77
|
+
def mac_app?
|
78
|
+
false
|
79
|
+
end
|
80
|
+
|
81
|
+
# Must overwrite with app-specific match
|
82
|
+
def windows_app?
|
83
|
+
false
|
84
|
+
end
|
85
|
+
|
86
|
+
def missing?
|
87
|
+
user_agent_string.nil?
|
88
|
+
end
|
89
|
+
|
90
|
+
def app_version
|
91
|
+
# App user-agent string is parsed into two separate UserAgent instances, it's the last one that contains the right version
|
92
|
+
user_agent.last.version if native?
|
93
|
+
end
|
94
|
+
|
95
|
+
def to_s
|
96
|
+
case
|
97
|
+
when ipad_app? then "iPad app"
|
98
|
+
when iphone_app? then "iPhone app"
|
99
|
+
when android_app? then "Android app"
|
100
|
+
when mac_app? then "Mac app"
|
101
|
+
when windows_app? then "Windows app"
|
102
|
+
when tablet? then "tablet"
|
103
|
+
when phone? then "phone"
|
104
|
+
when missing? then "missing"
|
105
|
+
else "web"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
private
|
110
|
+
attr_accessor :user_agent_string
|
111
|
+
|
112
|
+
def match?(pattern)
|
113
|
+
true if user_agent_string.to_s.match(pattern)
|
114
|
+
end
|
115
|
+
|
116
|
+
def user_agent
|
117
|
+
@user_agent ||= UserAgent.parse(user_agent_string)
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = 'platform_agent'
|
3
|
+
s.version = '1.0.0'
|
4
|
+
s.authors = 'David Heinemeier Hansson'
|
5
|
+
s.email = 'david@basecamp.com'
|
6
|
+
s.summary = 'Parse user agent to deduce the platform.'
|
7
|
+
s.homepage = 'https://github.com/basecamp/platform_agent'
|
8
|
+
s.license = 'MIT'
|
9
|
+
|
10
|
+
s.required_ruby_version = '>= 2.4.0'
|
11
|
+
|
12
|
+
s.add_dependency 'activesupport', '>= 5.2.0'
|
13
|
+
s.add_dependency 'useragent', '~> 0.16.3'
|
14
|
+
|
15
|
+
s.add_development_dependency 'activemodel', '>= 5.2.0'
|
16
|
+
s.add_development_dependency 'bundler', '~> 1.15'
|
17
|
+
|
18
|
+
s.files = `git ls-files`.split("\n")
|
19
|
+
s.test_files = `git ls-files -- test/*`.split("\n")
|
20
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'active_support'
|
2
|
+
require 'active_support/testing/autorun'
|
3
|
+
|
4
|
+
require 'platform_agent'
|
5
|
+
|
6
|
+
class BasecampAgent < PlatformAgent
|
7
|
+
def ios_app?
|
8
|
+
match?(/BC3 iOS/)
|
9
|
+
end
|
10
|
+
|
11
|
+
def android_app?
|
12
|
+
match? /BC3 Android/
|
13
|
+
end
|
14
|
+
|
15
|
+
def mac_app?
|
16
|
+
match?(/Electron/) && match?(/basecamp3/) && match?(/Macintosh/)
|
17
|
+
end
|
18
|
+
|
19
|
+
def windows_app?
|
20
|
+
match?(/Electron/) && match?(/basecamp3/) && match?(/Windows/)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class PlatformAgentTest < ActiveSupport::TestCase
|
25
|
+
WINDOWS_PHONE = 'Mozilla/5.0 (Mobile; Windows Phone 8.1; Android 4.0; ARM; Trident/7.0; Touch; rv:11.0; IEMobile/11.0; NOKIA; Lumia 520) like iPhone OS 7_0_3 Mac OS X AppleWebKit/537 (KHTML, like Gecko) Mobile Safari/537'
|
26
|
+
CHROME_DESKTOP = 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36'
|
27
|
+
CHROME_ANDROID = 'Mozilla/5.0 (Linux; Android 4.4; Nexus 5 Build/_BuildID_) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/30.0.0.0 Mobile Safari/537.36'
|
28
|
+
SAFARI_IPHONE = 'Mozilla/5.0 (iPhone; CPU iPhone OS 8_1 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) Version/8.0 Mobile/12B410 Safari/600.1.4'
|
29
|
+
SAFARI_IPAD = 'Mozilla/5.0 (iPad; CPU iPhone OS 8_1 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) Version/8.0 Mobile/12B410 Safari/600.1.4'
|
30
|
+
BASECAMP_MAC = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_3) AppleWebKit/537.36 (KHTML, like Gecko) basecamp3/1.0.2 Chrome/47.0.2526.110 Electron/0.36.7 Safari/537.36'
|
31
|
+
BASECAMP_WINDOWS = 'Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) basecamp3/1.0.2 Chrome/49.0.2623.75 Electron/0.36.7 Safari/537.36'
|
32
|
+
BASECAMP_IPAD = 'BC3 iOS/3.0.1 (build 13; iPad Air 2); iOS 9.3'
|
33
|
+
BASECAMP_IPHONE = 'BC3 iOS/3.0.1 (build 13; iPhone 6S); iOS 9.3'
|
34
|
+
BASECAMP_ANDROID = 'BC3 Android/3.0.1 (build 13; Galaxy S3); Marshmallow'
|
35
|
+
|
36
|
+
test "chrome is via web" do
|
37
|
+
assert_equal "web", platform(CHROME_DESKTOP)
|
38
|
+
end
|
39
|
+
|
40
|
+
test "safari iOS is via phone" do
|
41
|
+
assert_equal "phone", platform(SAFARI_IPHONE)
|
42
|
+
end
|
43
|
+
|
44
|
+
test "safari iPad is via tablet" do
|
45
|
+
assert_equal "tablet", platform(SAFARI_IPAD)
|
46
|
+
end
|
47
|
+
|
48
|
+
test "basecamp iPhone is via iPhone app" do
|
49
|
+
assert_equal "iPhone app", platform(BASECAMP_IPHONE)
|
50
|
+
end
|
51
|
+
|
52
|
+
test "basecamp iPad is via iPad app" do
|
53
|
+
assert_equal "iPad app", platform(BASECAMP_IPAD)
|
54
|
+
end
|
55
|
+
|
56
|
+
test "basecamp Android is via Android app" do
|
57
|
+
assert_equal "Android app", platform(BASECAMP_ANDROID)
|
58
|
+
end
|
59
|
+
|
60
|
+
test "basecamp Mac is via Mac app" do
|
61
|
+
assert_equal "Mac app", platform(BASECAMP_MAC)
|
62
|
+
end
|
63
|
+
|
64
|
+
test "basecamp Windows is via Windows app" do
|
65
|
+
assert_equal "Windows app", platform(BASECAMP_WINDOWS)
|
66
|
+
end
|
67
|
+
|
68
|
+
test "blank user agent is via missing" do
|
69
|
+
assert_equal "missing", platform(nil)
|
70
|
+
end
|
71
|
+
|
72
|
+
test "other phones are via phone" do
|
73
|
+
assert_equal "phone", platform(WINDOWS_PHONE)
|
74
|
+
end
|
75
|
+
|
76
|
+
test "non-native app android detection" do
|
77
|
+
assert android_phone?(CHROME_ANDROID), "CHROME_ANDROID should be android phone"
|
78
|
+
|
79
|
+
assert_not android_phone?(SAFARI_IPHONE), "SAFARI_IPHONE should not be android phone"
|
80
|
+
assert_not android_phone?(BASECAMP_ANDROID), "BC3_ANDROID should not be android phone"
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
def platform(agent)
|
85
|
+
BasecampAgent.new(agent).to_s
|
86
|
+
end
|
87
|
+
|
88
|
+
def android_phone?(agent)
|
89
|
+
BasecampAgent.new(agent).android_phone?
|
90
|
+
end
|
91
|
+
end
|
metadata
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: platform_agent
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- David Heinemeier Hansson
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2019-04-02 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activesupport
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 5.2.0
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 5.2.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: useragent
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.16.3
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 0.16.3
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: activemodel
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 5.2.0
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 5.2.0
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: bundler
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.15'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.15'
|
69
|
+
description:
|
70
|
+
email: david@basecamp.com
|
71
|
+
executables: []
|
72
|
+
extensions: []
|
73
|
+
extra_rdoc_files: []
|
74
|
+
files:
|
75
|
+
- Gemfile
|
76
|
+
- Gemfile.lock
|
77
|
+
- MIT-LICENSE
|
78
|
+
- README.md
|
79
|
+
- Rakefile
|
80
|
+
- lib/platform_agent.rb
|
81
|
+
- platform_agent.gemspec
|
82
|
+
- test/platform_agent_test.rb
|
83
|
+
homepage: https://github.com/basecamp/platform_agent
|
84
|
+
licenses:
|
85
|
+
- MIT
|
86
|
+
metadata: {}
|
87
|
+
post_install_message:
|
88
|
+
rdoc_options: []
|
89
|
+
require_paths:
|
90
|
+
- lib
|
91
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - ">="
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: 2.4.0
|
96
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
97
|
+
requirements:
|
98
|
+
- - ">="
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
version: '0'
|
101
|
+
requirements: []
|
102
|
+
rubyforge_project:
|
103
|
+
rubygems_version: 2.7.3
|
104
|
+
signing_key:
|
105
|
+
specification_version: 4
|
106
|
+
summary: Parse user agent to deduce the platform.
|
107
|
+
test_files:
|
108
|
+
- test/platform_agent_test.rb
|