smartcar 2.1.0 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.rubocop.yml +32 -0
- data/.travis.yml +1 -2
- data/Gemfile +4 -2
- data/Gemfile.lock +78 -20
- data/README.md +38 -36
- data/Rakefile +11 -3
- data/bin/console +4 -3
- data/lib/open_struct_extensions.rb +21 -0
- data/lib/smartcar.rb +173 -43
- data/lib/smartcar/auth_client.rb +154 -0
- data/lib/smartcar/base.rb +21 -29
- data/lib/smartcar/utils.rb +107 -35
- data/lib/smartcar/vehicle.rb +183 -252
- data/lib/smartcar/version.rb +3 -1
- data/lib/smartcar_error.rb +49 -0
- data/ruby-sdk.gemspec +27 -21
- metadata +71 -24
- data/lib/smartcar/battery.rb +0 -13
- data/lib/smartcar/battery_capacity.rb +0 -9
- data/lib/smartcar/charge.rb +0 -13
- data/lib/smartcar/engine_oil.rb +0 -12
- data/lib/smartcar/fuel.rb +0 -15
- data/lib/smartcar/location.rb +0 -10
- data/lib/smartcar/oauth.rb +0 -119
- data/lib/smartcar/odometer.rb +0 -9
- data/lib/smartcar/permissions.rb +0 -9
- data/lib/smartcar/tire_pressure.rb +0 -19
- data/lib/smartcar/user.rb +0 -26
- data/lib/smartcar/vehicle_attributes.rb +0 -12
- data/lib/smartcar/vin.rb +0 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3f96ead76c474cc7bd2f579c79ae5457437b7826ea5738fc939e78e7fd15ad9a
|
4
|
+
data.tar.gz: 4ca101d8945328f524a4c4fe525eef8c0fa9ed5828a6d7fc1e7d96bccc299015
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c944b360628c97f3e78e6d1f7287e91c136a33cd8c696c0e8246c18839f97d790aea5457dc402ace57eda565f4937dbc72a51e5c66e70b12d24a1850674f6bcf
|
7
|
+
data.tar.gz: 73646b1545692c60714bd7581abef94f6271907587dc1ed2564dbae30adafb63b3c3d546b46f2188933fd2f4035a1eb8922fa4ef0c7e09a4fbd48a5d69c1cb11
|
data/.gitignore
CHANGED
data/.rubocop.yml
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# Prevent messages for libraries of rubocop
|
2
|
+
AllCops:
|
3
|
+
NewCops: enable
|
4
|
+
SuggestExtensions: false
|
5
|
+
|
6
|
+
# Disabling ABC size for now as refactoring few places seems pointless for now
|
7
|
+
Metrics/AbcSize:
|
8
|
+
Enabled: false
|
9
|
+
|
10
|
+
# Disabling this becuase we are using `set` and `get` prefixed methods to keep some commonality across SDKs
|
11
|
+
Naming/AccessorMethodName:
|
12
|
+
Enabled: false
|
13
|
+
|
14
|
+
Metrics/BlockLength:
|
15
|
+
Exclude:
|
16
|
+
- '**/*.gemspec'
|
17
|
+
- 'spec/**/*'
|
18
|
+
|
19
|
+
# Just ignoring test helper for headless auth.
|
20
|
+
Metrics/MethodLength:
|
21
|
+
Max: 20
|
22
|
+
Exclude:
|
23
|
+
- 'spec/smartcar/helpers/auth_helper.rb'
|
24
|
+
|
25
|
+
# Parameters in data from json API comes in as camelCase, ignoring those files to avoid snake_case enforcement
|
26
|
+
Naming/MethodName:
|
27
|
+
Exclude:
|
28
|
+
- 'lib/smartcar/battery.rb'
|
29
|
+
- 'lib/smartcar/charge.rb'
|
30
|
+
- 'lib/smartcar/engine_oil.rb'
|
31
|
+
- 'lib/smartcar/fuel.rb'
|
32
|
+
- 'lib/smartcar/tire_pressure.rb'
|
data/.travis.yml
CHANGED
data/Gemfile
CHANGED
@@ -1,6 +1,8 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
source 'https://rubygems.org'
|
4
|
+
|
5
|
+
git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
|
4
6
|
|
5
7
|
# Specify your gem's dependencies in smartcar.gemspec
|
6
8
|
gemspec
|
data/Gemfile.lock
CHANGED
@@ -1,49 +1,103 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
smartcar (
|
4
|
+
smartcar (3.0.0)
|
5
5
|
oauth2 (~> 1.4)
|
6
6
|
|
7
7
|
GEM
|
8
8
|
remote: https://rubygems.org/
|
9
9
|
specs:
|
10
|
+
addressable (2.7.0)
|
11
|
+
public_suffix (>= 2.0.2, < 5.0)
|
12
|
+
ast (2.4.2)
|
13
|
+
backport (1.1.2)
|
10
14
|
byebug (11.1.3)
|
11
15
|
childprocess (3.0.0)
|
12
|
-
|
13
|
-
|
16
|
+
codecov (0.5.2)
|
17
|
+
simplecov (>= 0.15, < 0.22)
|
18
|
+
crack (0.4.5)
|
19
|
+
rexml
|
20
|
+
diff-lcs (1.4.4)
|
21
|
+
docile (1.4.0)
|
22
|
+
faraday (1.4.2)
|
23
|
+
faraday-em_http (~> 1.0)
|
24
|
+
faraday-em_synchrony (~> 1.0)
|
25
|
+
faraday-excon (~> 1.1)
|
26
|
+
faraday-net_http (~> 1.0)
|
27
|
+
faraday-net_http_persistent (~> 1.1)
|
14
28
|
multipart-post (>= 1.2, < 3)
|
15
|
-
ruby2_keywords
|
16
|
-
|
29
|
+
ruby2_keywords (>= 0.0.4)
|
30
|
+
faraday-em_http (1.0.0)
|
31
|
+
faraday-em_synchrony (1.0.0)
|
32
|
+
faraday-excon (1.1.0)
|
33
|
+
faraday-net_http (1.0.1)
|
34
|
+
faraday-net_http_persistent (1.1.0)
|
35
|
+
hashdiff (1.0.1)
|
36
|
+
jwt (2.2.3)
|
17
37
|
multi_json (1.15.0)
|
18
38
|
multi_xml (0.6.0)
|
19
39
|
multipart-post (2.1.1)
|
20
|
-
oauth2 (1.4.
|
40
|
+
oauth2 (1.4.7)
|
21
41
|
faraday (>= 0.8, < 2.0)
|
22
42
|
jwt (>= 1.0, < 3.0)
|
23
43
|
multi_json (~> 1.3)
|
24
44
|
multi_xml (~> 0.5)
|
25
45
|
rack (>= 1.2, < 3)
|
46
|
+
parallel (1.20.1)
|
47
|
+
parser (3.0.1.1)
|
48
|
+
ast (~> 2.4.1)
|
49
|
+
public_suffix (4.0.6)
|
26
50
|
rack (2.2.3)
|
51
|
+
rainbow (3.0.0)
|
27
52
|
rake (12.3.3)
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
53
|
+
readapt (1.3.0)
|
54
|
+
backport (~> 1.1)
|
55
|
+
thor (~> 1.0)
|
56
|
+
redcarpet (3.5.1)
|
57
|
+
regexp_parser (2.1.1)
|
58
|
+
rexml (3.2.5)
|
59
|
+
rspec (3.10.0)
|
60
|
+
rspec-core (~> 3.10.0)
|
61
|
+
rspec-expectations (~> 3.10.0)
|
62
|
+
rspec-mocks (~> 3.10.0)
|
63
|
+
rspec-core (3.10.1)
|
64
|
+
rspec-support (~> 3.10.0)
|
65
|
+
rspec-expectations (3.10.1)
|
36
66
|
diff-lcs (>= 1.2.0, < 2.0)
|
37
|
-
rspec-support (~> 3.
|
38
|
-
rspec-mocks (3.
|
67
|
+
rspec-support (~> 3.10.0)
|
68
|
+
rspec-mocks (3.10.2)
|
39
69
|
diff-lcs (>= 1.2.0, < 2.0)
|
40
|
-
rspec-support (~> 3.
|
41
|
-
rspec-support (3.
|
42
|
-
|
70
|
+
rspec-support (~> 3.10.0)
|
71
|
+
rspec-support (3.10.2)
|
72
|
+
rubocop (1.16.1)
|
73
|
+
parallel (~> 1.10)
|
74
|
+
parser (>= 3.0.0.0)
|
75
|
+
rainbow (>= 2.2.2, < 4.0)
|
76
|
+
regexp_parser (>= 1.8, < 3.0)
|
77
|
+
rexml
|
78
|
+
rubocop-ast (>= 1.7.0, < 2.0)
|
79
|
+
ruby-progressbar (~> 1.7)
|
80
|
+
unicode-display_width (>= 1.4.0, < 3.0)
|
81
|
+
rubocop-ast (1.7.0)
|
82
|
+
parser (>= 3.0.1.1)
|
83
|
+
ruby-progressbar (1.11.0)
|
84
|
+
ruby2_keywords (0.0.4)
|
43
85
|
rubyzip (2.3.0)
|
44
86
|
selenium-webdriver (3.142.7)
|
45
87
|
childprocess (>= 0.5, < 4.0)
|
46
88
|
rubyzip (>= 1.2.2)
|
89
|
+
simplecov (0.21.2)
|
90
|
+
docile (~> 1.1)
|
91
|
+
simplecov-html (~> 0.11)
|
92
|
+
simplecov_json_formatter (~> 0.1)
|
93
|
+
simplecov-html (0.12.3)
|
94
|
+
simplecov_json_formatter (0.1.3)
|
95
|
+
thor (1.1.0)
|
96
|
+
unicode-display_width (2.0.0)
|
97
|
+
webmock (3.13.0)
|
98
|
+
addressable (>= 2.3.6)
|
99
|
+
crack (>= 0.3.2)
|
100
|
+
hashdiff (>= 0.4.0, < 2.0.0)
|
47
101
|
|
48
102
|
PLATFORMS
|
49
103
|
ruby
|
@@ -51,11 +105,15 @@ PLATFORMS
|
|
51
105
|
DEPENDENCIES
|
52
106
|
bundler (~> 2.0)
|
53
107
|
byebug (~> 11.0)
|
108
|
+
codecov (~> 0.5.2)
|
54
109
|
rake (~> 12.3, >= 12.3.3)
|
110
|
+
readapt (~> 1.3)
|
55
111
|
redcarpet (~> 3.5.0)
|
56
112
|
rspec (~> 3.0)
|
113
|
+
rubocop (~> 1.12)
|
57
114
|
selenium-webdriver (~> 3.142)
|
58
115
|
smartcar!
|
116
|
+
webmock (~> 3.13)
|
59
117
|
|
60
118
|
BUNDLED WITH
|
61
|
-
2.1.
|
119
|
+
2.1.4
|
data/README.md
CHANGED
@@ -29,28 +29,28 @@ not have access to the dashboard, please
|
|
29
29
|
|
30
30
|
### Flow
|
31
31
|
|
32
|
-
- Create a new `AuthClient` object with your `
|
33
|
-
`
|
34
|
-
- Redirect the user to Smartcar Connect using `
|
32
|
+
- Create a new `AuthClient` object with your `client_id`, `client_secret`,
|
33
|
+
`redirect_uri`.
|
34
|
+
- Redirect the user to Smartcar Connect using `get_auth_url` with required `scope` or with one
|
35
35
|
of our frontend SDKs.
|
36
36
|
- The user will login, and then accept or deny your `scope`'s permissions.
|
37
|
-
- Handle the get request to `
|
37
|
+
- Handle the get request to `redirect_uri`.
|
38
38
|
- If the user accepted your permissions, `req.query.code` will contain an
|
39
39
|
authorization code.
|
40
|
-
- Use `
|
40
|
+
- Use `exchange_code` with this code to obtain an access object
|
41
41
|
containing an access token (lasting 2 hours) and a refresh token
|
42
42
|
(lasting 60 days).
|
43
43
|
- Save this access object.
|
44
44
|
- If the user denied your permissions, `req.query.error` will be set
|
45
45
|
to `"access_denied"`.
|
46
|
-
- If you passed a state parameter to `
|
46
|
+
- If you passed a state parameter to `get_auth_url`, `req.query.state` will
|
47
47
|
contain the state value.
|
48
|
-
- Get the user's vehicles with `
|
49
|
-
- Create a new `Vehicle` object using a `
|
48
|
+
- Get the user's vehicles with `getVehicles`.
|
49
|
+
- Create a new `Vehicle` object using a `vehicle_id` from the previous response,
|
50
50
|
and the `access_token`.
|
51
51
|
- Make requests to the Smartcar API.
|
52
|
-
- Use `
|
53
|
-
when your `
|
52
|
+
- Use `exchange_refresh_token` on your saved `refresh_token` to retrieve a new token
|
53
|
+
when your `access_token` expires.
|
54
54
|
|
55
55
|
## Installation
|
56
56
|
|
@@ -70,53 +70,47 @@ Or install it yourself as:
|
|
70
70
|
|
71
71
|
## Usage
|
72
72
|
|
73
|
-
Setup the environment variables for
|
73
|
+
Setup the environment variables for SMARTCAR_CLIENT_ID, SMARTCAR_CLIENT_SECRET and SMARTCAR_REDIRECT_URI.
|
74
74
|
```bash
|
75
75
|
# Get your API keys from https://dashboard.smartcar.com/signup
|
76
|
-
export
|
77
|
-
export
|
76
|
+
export SMARTCAR_CLIENT_ID=<client id>
|
77
|
+
export SMARTCAR_CLIENT_SECRET=<client secret>
|
78
|
+
export SMARTCAR_REDIRECT_URI=<redirect URI>
|
78
79
|
```
|
79
80
|
|
80
81
|
Example Usage for calling the reports API with oAuth token
|
81
82
|
```ruby
|
82
83
|
2.5.7 :001 > require 'smartcar'
|
83
84
|
=> true
|
84
|
-
2.5.7 :003 > ids = Smartcar::Vehicle.
|
85
|
+
2.5.7 :003 > ids = Smartcar::Vehicle.get_vehicles(token: token).vehicles
|
85
86
|
=> ["4bb777b2-bde7-4305-8952-25956f8c0868"]
|
86
87
|
2.5.7 :004 > vehicle = Smartcar::Vehicle.new(token: token, id: ids.first)
|
87
|
-
=> #<Smartcar::Vehicle:
|
88
|
+
=> #<Smartcar::Vehicle:0x0000558dcd7ee608 @token="c900e00e-ee8e-403d-a7bf-f992bc0ad302", @id="e31c9de6-1332-472b-b648-5d74b05b7fda", @options={:unit_system=>"metric", :version=>"2.0"}, @unit_system="metric", @version="2.0", @service=#<Faraday::Connection:0x0000558dcd7d63f0 @parallel_manager=nil, @headers={"User-Agent"=>"Faraday v1.4.2"}, @params={}, @options=#<Faraday::RequestOptions timeout=310>, @ssl=#<Faraday::SSLOptions verify=true>, @default_parallel_manager=nil, @builder=#<Faraday::RackBuilder:0x0000558dcd7c1bf8 @adapter=Faraday::Adapter::NetHttp, @handlers=[Faraday::Request::UrlEncoded], @app=#<Faraday::Request::UrlEncoded:0x0000558dcd7af048 @app=#<Faraday::Adapter::NetHttp:0x0000558dcd7af390 @ssl_cert_store=#<OpenSSL::X509::Store:0x0000558dcd7a36a8 @verify_callback=nil, @error=nil, @error_string=nil, @chain=nil, @time=nil>, @app=#<Proc:0x0000558dcd7af278 /home/ashwinsubramanian/.rvm/gems/ruby-2.7.2/gems/faraday-1.4.2/lib/faraday/adapter.rb:37 (lambda)>, @connection_options={}, @config_block=nil>, @options={}>>, @url_prefix=#<URI::HTTPS https://api.smartcar.com/>, @proxy=nil, @manual_proxy=false>>
|
88
89
|
2.5.7 :006 > vehicle.odometer
|
89
|
-
=> #<
|
90
|
+
=> #<OpenStruct distance=39685.33984375, meta=#<OpenStruct data_age=#<DateTime: 2021-06-24T22:28:39+00:00 ((2459390j,80919s,95000000n),+0s,2299161j)>, unit_system="metric", request_id="4962ba7f-5c94-48ab-9955-4e2b101c7b8a">>
|
90
91
|
2.5.7 :007 > vehicle.battery
|
91
|
-
=> #<
|
92
|
+
=> #<OpenStruct range=208.82, percentRemaining=0.31, meta=#<OpenStruct data_age=#<DateTime: 2021-06-24T22:28:54+00:00 ((2459390j,80934s,855000000n),+0s,2299161j)>, unit_system="metric", request_id="a88b95ec-b10f-4fc8-979b-5d95fe40d925">, percentage_remaining=0.31>
|
92
93
|
2.5.7 :009 > vehicle.lock!
|
93
|
-
=>
|
94
|
-
2.5.7 :010 > vehicle.batch(["charge","battery"])
|
95
|
-
=>
|
96
|
-
2.5.7 :011 >
|
97
|
-
|
98
|
-
5: from /usr/share/rvm/rubies/ruby-2.5.7/bin/irb:11:in `<main>'
|
99
|
-
4: from (irb):5
|
100
|
-
3: from /home/st-2vgpnn2/.rvm/gems/ruby-2.5.7/gems/smartcar-1.0.0/lib/smartcar/vehicle.rb:118:in `start_charge!'
|
101
|
-
2: from /home/st-2vgpnn2/.rvm/gems/ruby-2.5.7/gems/smartcar-1.0.0/lib/smartcar/vehicle.rb:290:in `start_or_stop_charge!'
|
102
|
-
1: from /home/st-2vgpnn2/.rvm/gems/ruby-2.5.7/gems/smartcar-1.0.0/lib/smartcar/base.rb:39:in `block (2 levels) in <class:Base>'
|
103
|
-
Smartcar::ExternalServiceError (API error - {"error":"vehicle_state_error","message":"Charging plug is not connected to the vehicle.","code":"VS_004"})
|
104
|
-
|
94
|
+
=> #<OpenStruct status="success", message="Successfully sent request to vehicle", meta=#<OpenStruct request_id="0c90918f-a9cc-405c-839f-7d9b70e249c4">>
|
95
|
+
2.5.7 :010 > batch_response = vehicle.batch(["/charge","/battery"])
|
96
|
+
=> #<OpenStruct>
|
97
|
+
2.5.7 :011 > batch_response.charge()
|
98
|
+
=> #<OpenStruct state="NOT_CHARGING", isPluggedIn=false, meta=#<OpenStruct data_age=#<DateTime: 2021-06-24T22:30:20+00:00 ((2459390j,81020s,892000000n),+0s,2299161j)>, request_id="29a66280-8685-4a57-9733-daa3dfb9970f">, is_plugged_in?=false>
|
105
99
|
```
|
106
100
|
|
107
101
|
Example Usage for oAuth -
|
108
102
|
```ruby
|
109
103
|
# To get the redirect URL :
|
110
|
-
2.5.5 :002 > options = {test_mode: true
|
104
|
+
2.5.5 :002 > options = {test_mode: true}
|
111
105
|
2.5.5 :003 > require 'smartcar'
|
112
|
-
2.5.5 :004 > client = Smartcar::
|
113
|
-
2.5.5 :005 > url = client.
|
106
|
+
2.5.5 :004 > client = Smartcar::AuthClient.new(options)
|
107
|
+
2.5.5 :005 > url = client.get_auth_url(["read_battery","read_charge","read_fuel","read_location","control_security","read_odometer","read_tires","read_vin","read_vehicle_info"], {flags: ["country:DE"]})
|
114
108
|
=> "https://connect.smartcar.com/oauth/authorize?approval_prompt=auto&client_id=<client id>&mode=test&redirect_uri=http%3A%2F%2Flocalhost%3A8000%2Fcallback&response_type=code&scope=read_battery+read_charge+read_fuel+read_location+control_security+read_odometer+read_tires+read_vin+read_vehicle_info&flags=country%3ADE"
|
115
109
|
# Redirect user to the above URL.
|
116
110
|
# After authentication user control reaches the callback URL with code.
|
117
111
|
# Use the code from the parameters and request a token
|
118
|
-
2.5.5 :006 > token_hash = client.
|
119
|
-
=>
|
112
|
+
2.5.5 :006 > token_hash = client.exchange_code(code)
|
113
|
+
=> #<OpenStruct token_type="Bearer", access_token="20e24b4a-3055-4cc8-9cf3-2b3c5afba3e6", refresh_token="cf89c62e-7b36-4e13-a9df-d9c2a5296280", expires_at=1624581588>
|
120
114
|
# This access_token can be used to call the Smartcar APIs as given above.
|
121
115
|
# Store this hash and if it expired refresh the token OR use the code again to
|
122
116
|
# get a new token or use .
|
@@ -128,8 +122,10 @@ To install this gem onto your local machine, run `bundle exec rake install`.
|
|
128
122
|
|
129
123
|
To run tests, make sure you have the env variables setup for client id and secret.
|
130
124
|
```shell
|
131
|
-
export
|
132
|
-
export
|
125
|
+
export E2E_SMARTCAR_CLIENT_ID=<client id>
|
126
|
+
export E2E_SMARTCAR_CLIENT_SECRET=<client secret>
|
127
|
+
export E2E_SMARTCAR_AMT=<amt from dashboard for webhooks>
|
128
|
+
export E2E_SMARTCAR_WEBHOOK_ID=<webhook id to use for tests>
|
133
129
|
```
|
134
130
|
|
135
131
|
Tests can be run using either default rake command OR specific rspec command.
|
@@ -164,3 +160,9 @@ To contribute, please:
|
|
164
160
|
|
165
161
|
[gem-image]: https://badge.fury.io/rb/smartcar
|
166
162
|
[gem-url]: https://badge.fury.io/rb/smartcar.svg
|
163
|
+
|
164
|
+
## Supported Ruby Branches
|
165
|
+
|
166
|
+
Smartcar aims to support the SDK on all Ruby branches that have a status of "normal maintenance" or "security maintenance" as defined in the [Ruby Branches documentation](https://www.ruby-lang.org/en/downloads/branches/).
|
167
|
+
|
168
|
+
In accordance with the Semantic Versioning specification, the addition of support for new Ruby branches would result in a MINOR version bump and the removal of support for Ruby branches would result in a MAJOR version bump.
|
data/Rakefile
CHANGED
@@ -1,6 +1,14 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/gem_tasks'
|
4
|
+
require 'rspec/core/rake_task'
|
3
5
|
|
4
6
|
RSpec::Core::RakeTask.new(:spec)
|
5
7
|
|
6
|
-
|
8
|
+
require 'rubocop/rake_task'
|
9
|
+
|
10
|
+
RuboCop::RakeTask.new(:rubocop) do |t|
|
11
|
+
t.options = ['--display-cop-names']
|
12
|
+
end
|
13
|
+
|
14
|
+
task default: %i[rubocop spec]
|
data/bin/console
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
2
3
|
|
3
|
-
require
|
4
|
-
require
|
4
|
+
require 'bundler/setup'
|
5
|
+
require 'smartcar'
|
5
6
|
|
6
7
|
# You can add fixtures and/or initialization code here to make experimenting
|
7
8
|
# with your gem easier. You can also use a different console, if you like.
|
@@ -10,5 +11,5 @@ require "smartcar"
|
|
10
11
|
# require "pry"
|
11
12
|
# Pry.start
|
12
13
|
|
13
|
-
require
|
14
|
+
require 'irb'
|
14
15
|
IRB.start(__FILE__)
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Extension to OpenStruct to convert a nested OpenStruct object to a hash.
|
4
|
+
# Using this method any of the API response can be converted back to hash
|
5
|
+
# or JSON (from hash) for convenience.
|
6
|
+
# Example Usage :
|
7
|
+
# response = {a: { b: {c: "test", d: [{x: 1}, {y: 3}]}}}
|
8
|
+
class OpenStruct
|
9
|
+
def deep_to_h
|
10
|
+
to_h.transform_values do |value|
|
11
|
+
case value
|
12
|
+
when is_a?(OpenStruct)
|
13
|
+
value.deep_to_h
|
14
|
+
when is_a?(Array)
|
15
|
+
value.map { |item| item.is_a?(OpenStruct) ? item.deep_to_h : item }
|
16
|
+
else
|
17
|
+
value
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/lib/smartcar.rb
CHANGED
@@ -1,54 +1,184 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
7
|
-
require
|
8
|
-
require
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
require "smartcar/odometer"
|
13
|
-
require "smartcar/tire_pressure"
|
14
|
-
require "smartcar/vin"
|
15
|
-
require "smartcar/vehicle_attributes"
|
16
|
-
require "smartcar/vehicle"
|
17
|
-
require "smartcar/user"
|
18
|
-
|
19
|
-
|
20
|
-
# Main Smartcar umbrella module
|
21
|
-
module Smartcar
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'smartcar_error'
|
4
|
+
require 'smartcar/utils'
|
5
|
+
require 'smartcar/version'
|
6
|
+
require 'smartcar/base'
|
7
|
+
require 'smartcar/auth_client'
|
8
|
+
require 'smartcar/vehicle'
|
9
|
+
|
10
|
+
# Main Smartcar umbrella module
|
11
|
+
module Smartcar
|
22
12
|
# Error raised when a config is not found
|
23
13
|
class ConfigNotFound < StandardError; end
|
24
|
-
|
25
|
-
class ExternalServiceError < StandardError; end
|
26
|
-
# Error raised when Smartcar returns 404
|
27
|
-
class ServiceUnavailableError < ExternalServiceError; end
|
28
|
-
# Error raised when Smartcar returns Authentication Error with status 401
|
29
|
-
class AuthenticationError < ExternalServiceError; end
|
30
|
-
# Error raised when Smartcar returns 400 response
|
31
|
-
class BadRequestError < ExternalServiceError; end
|
32
|
-
# Smartcar API version being used
|
33
|
-
API_VERSION = "v1.0".freeze
|
14
|
+
|
34
15
|
# Host to connect to smartcar
|
35
|
-
|
16
|
+
API_ORIGIN = 'https://api.smartcar.com/'
|
17
|
+
PATHS = {
|
18
|
+
compatibility: '/compatibility',
|
19
|
+
user: '/user',
|
20
|
+
vehicles: '/vehicles'
|
21
|
+
}.freeze
|
36
22
|
|
37
23
|
# Path for smartcar oauth
|
38
|
-
|
39
|
-
%w
|
24
|
+
AUTH_ORIGIN = 'https://connect.smartcar.com'
|
25
|
+
%w[success code test live force auto metric imperial].each do |constant|
|
40
26
|
# Constant to represent the value
|
41
27
|
const_set(constant.upcase, constant.freeze)
|
42
28
|
end
|
43
29
|
|
44
|
-
# Lock value sent in request body
|
45
|
-
LOCK = "LOCK".freeze
|
46
|
-
# Unlock value sent in request body
|
47
|
-
UNLOCK = "UNLOCK".freeze
|
48
|
-
# Start charge value sent in request body
|
49
|
-
START_CHARGE = "START".freeze
|
50
|
-
# Stop charge value sent in request body
|
51
|
-
STOP_CHARGE = "STOP".freeze
|
52
30
|
# Constant for units
|
53
|
-
UNITS = [IMPERIAL,METRIC]
|
31
|
+
UNITS = [IMPERIAL, METRIC].freeze
|
32
|
+
|
33
|
+
# Smartcar API version variable - defaulted to 2.0
|
34
|
+
@api_version = '2.0'
|
35
|
+
|
36
|
+
class << self
|
37
|
+
# Module method Used to set api version to be used.
|
38
|
+
# This method can be used at the top to set the version and any
|
39
|
+
# following request will use the version set here unless overridden
|
40
|
+
# separately.
|
41
|
+
# @param version [String] version to be set without 'v' prefix.
|
42
|
+
def set_api_version(version)
|
43
|
+
instance_variable_set('@api_version', version)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Module method Used to get api version to be used.
|
47
|
+
# This is the getter for the class instance variable @api_version
|
48
|
+
#
|
49
|
+
# @return [String] api version number without 'v' prefix
|
50
|
+
def get_api_version
|
51
|
+
instance_variable_get('@api_version')
|
52
|
+
end
|
53
|
+
|
54
|
+
# Module method Used to check compatiblity for VIN and scope
|
55
|
+
#
|
56
|
+
# API Documentation - https://smartcar.com/docs/api#connect-compatibility
|
57
|
+
# @param vin [String] VIN of the vehicle to be checked
|
58
|
+
# @param scope [Array of Strings] - array of scopes
|
59
|
+
# @param country [String] An optional country code according to
|
60
|
+
# [ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2).
|
61
|
+
# Defaults to US.
|
62
|
+
# @param options [Hash] Other optional parameters including overrides
|
63
|
+
# @option options [String] :client_id Client ID that overrides ENV
|
64
|
+
# @option options [String] :client_secret Client Secret that overrides ENV
|
65
|
+
# @option options [String] :version API version to use, defaults to what is globally set
|
66
|
+
# @option options [Hash] :flags A hash of flag name string as key and a string or boolean value.
|
67
|
+
# @option options [Boolean] :test_mode Wether to use test mode or not.
|
68
|
+
# @option options [String] :test_mode_compatibility_level this is required argument while using
|
69
|
+
# test mode with a real vin. For more information refer to docs.
|
70
|
+
#
|
71
|
+
# @return [OpenStruct] And object representing the JSON response mentioned in https://smartcar.com/docs/api#connect-compatibility
|
72
|
+
# and a meta attribute with the relevant items from response headers.
|
73
|
+
def get_compatibility(vin:, scope:, country: 'US', options: {})
|
74
|
+
raise InvalidParameterValue.new, 'vin is a required field' if vin.nil?
|
75
|
+
raise InvalidParameterValue.new, 'scope is a required field' if scope.nil?
|
76
|
+
|
77
|
+
base_object = Base.new(
|
78
|
+
{
|
79
|
+
version: options[:version] || Smartcar.get_api_version,
|
80
|
+
auth_type: Base::BASIC
|
81
|
+
}
|
82
|
+
)
|
83
|
+
|
84
|
+
base_object.token = generate_basic_auth(options, base_object)
|
85
|
+
|
86
|
+
base_object.build_response(*base_object.fetch(
|
87
|
+
{
|
88
|
+
path: PATHS[:compatibility],
|
89
|
+
query_params: build_compatibility_params(vin, scope, country, options)
|
90
|
+
}
|
91
|
+
))
|
92
|
+
end
|
93
|
+
|
94
|
+
# Module method Used to get user id
|
95
|
+
#
|
96
|
+
# API Documentation - https://smartcar.com/docs/api#get-user
|
97
|
+
# @param token [String] Access token
|
98
|
+
#
|
99
|
+
# @return [OpenStruct] And object representing the JSON response mentioned in https://smartcar.com/docs/api#get-user
|
100
|
+
# and a meta attribute with the relevant items from response headers.
|
101
|
+
def get_user(token:, version: Smartcar.get_api_version)
|
102
|
+
base_object = Base.new(
|
103
|
+
{
|
104
|
+
token: token,
|
105
|
+
version: version
|
106
|
+
}
|
107
|
+
)
|
108
|
+
base_object.build_response(*base_object.fetch({ path: PATHS[:user] }))
|
109
|
+
end
|
110
|
+
|
111
|
+
# Module method Returns a paged list of all vehicles connected to the application for the current authorized user.
|
112
|
+
#
|
113
|
+
# API Documentation - https://smartcar.com/docs/api#get-all-vehicles
|
114
|
+
# @param token [String] - Access token
|
115
|
+
# @param paging [Hash] - Optional filter parameters (check documentation)
|
116
|
+
#
|
117
|
+
# @return [OpenStruct] And object representing the JSON response mentioned in https://smartcar.com/docs/api#get-all-vehicles
|
118
|
+
# and a meta attribute with the relevant items from response headers.
|
119
|
+
def get_vehicles(token:, paging: {}, version: Smartcar.get_api_version)
|
120
|
+
base_object = Base.new(
|
121
|
+
{
|
122
|
+
token: token,
|
123
|
+
version: version
|
124
|
+
}
|
125
|
+
)
|
126
|
+
base_object.build_response(*base_object.fetch(
|
127
|
+
{
|
128
|
+
path: PATHS[:vehicles],
|
129
|
+
query_params: paging
|
130
|
+
}
|
131
|
+
))
|
132
|
+
end
|
133
|
+
|
134
|
+
# Module method to generate hash challenge for webhooks. It does HMAC_SHA256(amt, challenge)
|
135
|
+
#
|
136
|
+
# @param amt [String] - Application Management Token
|
137
|
+
# @param challenge [String] - Challenge string
|
138
|
+
#
|
139
|
+
# @return [String] String representing the hex digest
|
140
|
+
def hash_challenge(amt, challenge)
|
141
|
+
OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'), amt, challenge)
|
142
|
+
end
|
143
|
+
|
144
|
+
# Module method used to verify webhook payload with AMT and signature.
|
145
|
+
#
|
146
|
+
# @param amt [String] - Application Management Token
|
147
|
+
# @param signature [String] - sc-signature header value
|
148
|
+
# @param body [Object] - webhook response body
|
149
|
+
#
|
150
|
+
# @return [true, false] - true if signature matches the hex digest of amt and body
|
151
|
+
def verify_payload(amt, signature, body)
|
152
|
+
hash_challenge(amt, body.to_json) == signature
|
153
|
+
end
|
154
|
+
|
155
|
+
private
|
156
|
+
|
157
|
+
def build_compatibility_params(vin, scope, country, options)
|
158
|
+
query_params = {
|
159
|
+
vin: vin,
|
160
|
+
scope: scope.join(' '),
|
161
|
+
country: country
|
162
|
+
}
|
163
|
+
query_params[:flags] = options[:flags].map { |key, value| "#{key}:#{value}" }.join(' ') if options[:flags]
|
164
|
+
query_params[:mode] = options[:test_mode].is_a?(TrueClass) ? 'test' : 'live' unless options[:test_mode].nil?
|
165
|
+
|
166
|
+
if options[:test_mode_compatibility_level]
|
167
|
+
query_params[:test_mode_compatibility_level] =
|
168
|
+
options[:test_mode_compatibility_level]
|
169
|
+
query_params[:mode] = 'test'
|
170
|
+
end
|
171
|
+
|
172
|
+
query_params
|
173
|
+
end
|
174
|
+
|
175
|
+
# returns auth token for Basic auth
|
176
|
+
#
|
177
|
+
# @return [String] Base64 encoding of CLIENT:SECRET
|
178
|
+
def generate_basic_auth(options, base_object)
|
179
|
+
client_id = options[:client_id] || base_object.get_config('SMARTCAR_CLIENT_ID')
|
180
|
+
client_secret = options[:client_secret] || base_object.get_config('SMARTCAR_CLIENT_SECRET')
|
181
|
+
Base64.strict_encode64("#{client_id}:#{client_secret}")
|
182
|
+
end
|
183
|
+
end
|
54
184
|
end
|