ruze 0.2.0 → 0.3.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 +4 -4
- data/.rubocop.yml +3 -1
- data/README.md +27 -0
- data/lib/ruze/car.rb +46 -6
- data/lib/ruze/errors.rb +4 -0
- data/lib/ruze/gigya.rb +29 -5
- data/lib/ruze/kamereon.rb +15 -3
- data/lib/ruze/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 328fbd59ffa961117d42c42029132b42d2109858f2a6f365d067019430d20dba
|
|
4
|
+
data.tar.gz: eb41527948e38b6d26a713e33afc1a7cf54c8616f1f0360e1e9394eb1e8cba39
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 30b962007b6fb90f95c50ced022dbc381f23f10a015627c0550521e01366a80b74f53b8aa18d79635590b77b0f57f307bc5af838b44209d3dff8b8e1aa72a4ed
|
|
7
|
+
data.tar.gz: 433d7097c27ea567c3a19387e8d04f6d0f167bac0be43d52ccaf3363ad7df7a1fb6cb76bfbfcf01178f392e55783ef4d8ac447a09f4d80e1da1ab5b8ab891803
|
data/.rubocop.yml
CHANGED
data/README.md
CHANGED
|
@@ -68,6 +68,33 @@ car.location
|
|
|
68
68
|
# }
|
|
69
69
|
```
|
|
70
70
|
|
|
71
|
+
### Repeated queries
|
|
72
|
+
|
|
73
|
+
A `Ruze::Car` keeps the data it fetched. To get new data, call `reload`
|
|
74
|
+
and query the car again:
|
|
75
|
+
|
|
76
|
+
```ruby
|
|
77
|
+
loop do
|
|
78
|
+
car.reload
|
|
79
|
+
puts car.battery['batteryLevel']
|
|
80
|
+
sleep 600
|
|
81
|
+
end
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Use one car for all queries. The car logs in once and keeps the login. It
|
|
85
|
+
gets a new token from Renault when the old token expires (after 15 minutes).
|
|
86
|
+
If Renault rejects the login later (HTTP 401 or 403), the car logs in again
|
|
87
|
+
and repeats the query once. Other errors, like a failing server, raise
|
|
88
|
+
`Ruze::Error` without a new login. A new `Ruze::Car` for each query makes 4 more requests, because
|
|
89
|
+
it logs in again and looks up the account and the VIN.
|
|
90
|
+
|
|
91
|
+
To log each login and each new token, give a logger. Any object with an
|
|
92
|
+
`info` method works:
|
|
93
|
+
|
|
94
|
+
```ruby
|
|
95
|
+
car = Ruze::Car.new(email, password, logger: Logger.new($stdout))
|
|
96
|
+
```
|
|
97
|
+
|
|
71
98
|
|
|
72
99
|
## Two-factor authentication
|
|
73
100
|
|
data/lib/ruze/car.rb
CHANGED
|
@@ -2,22 +2,62 @@ require_relative 'gigya'
|
|
|
2
2
|
require_relative 'kamereon'
|
|
3
3
|
|
|
4
4
|
module Ruze
|
|
5
|
+
# One Car can be queried repeatedly: it keeps the login and renews the JWT
|
|
6
|
+
# when needed. Call #reload to fetch fresh vehicle data.
|
|
7
|
+
#
|
|
8
|
+
# logger is any object with #info. It gets a message for each login and
|
|
9
|
+
# each new token.
|
|
5
10
|
class Car
|
|
6
|
-
def initialize(email, password, vin = nil, device: Ruze::Device.new)
|
|
7
|
-
|
|
8
|
-
@
|
|
11
|
+
def initialize(email, password, vin = nil, device: Ruze::Device.new, logger: nil)
|
|
12
|
+
@email = email
|
|
13
|
+
@password = password
|
|
14
|
+
@vin = vin
|
|
15
|
+
@device = device
|
|
16
|
+
@logger = logger
|
|
17
|
+
@gigya = new_gigya
|
|
9
18
|
end
|
|
10
19
|
|
|
11
20
|
def battery
|
|
12
|
-
|
|
21
|
+
with_session { kamereon.battery }
|
|
13
22
|
end
|
|
14
23
|
|
|
15
24
|
def cockpit
|
|
16
|
-
|
|
25
|
+
with_session { kamereon.cockpit }
|
|
17
26
|
end
|
|
18
27
|
|
|
19
28
|
def location
|
|
20
|
-
|
|
29
|
+
with_session { kamereon.location }
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Forgets the vehicle data, but keeps the login
|
|
33
|
+
def reload
|
|
34
|
+
@kamereon&.reload
|
|
35
|
+
self
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def kamereon
|
|
41
|
+
@kamereon ||= Ruze::Kamereon.new(@gigya.person_id, -> { @gigya.jwt }, @vin)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def new_gigya
|
|
45
|
+
Ruze::Gigya.new(@email, @password, device: @device, logger: @logger)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# A reused login can expire. If Renault rejects it, log in again and retry
|
|
49
|
+
# once. A fresh login gets no retry, because it would fail the same way.
|
|
50
|
+
# Other errors (like a failing server) get no new login.
|
|
51
|
+
def with_session
|
|
52
|
+
reused = !@kamereon.nil?
|
|
53
|
+
yield
|
|
54
|
+
rescue AuthenticationError => e
|
|
55
|
+
raise unless reused
|
|
56
|
+
|
|
57
|
+
@logger&.info("Login expired (#{e.message}), logging in again")
|
|
58
|
+
@gigya = new_gigya
|
|
59
|
+
@kamereon = nil
|
|
60
|
+
yield
|
|
21
61
|
end
|
|
22
62
|
end
|
|
23
63
|
end
|
data/lib/ruze/errors.rb
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
module Ruze
|
|
2
2
|
class Error < StandardError; end
|
|
3
3
|
|
|
4
|
+
# Raised when Renault rejects the login or the token (HTTP 401 or 403).
|
|
5
|
+
# A new login can help.
|
|
6
|
+
class AuthenticationError < Error; end
|
|
7
|
+
|
|
4
8
|
# Raised when Renault/Gigya requires two-factor authentication and no trusted
|
|
5
9
|
# device is available. Run the bootstrap flow (request_verification_code +
|
|
6
10
|
# verify_code) to establish a trusted device.
|
data/lib/ruze/gigya.rb
CHANGED
|
@@ -8,12 +8,19 @@ module Ruze
|
|
|
8
8
|
BASE_URL = 'https://accounts.eu1.gigya.com'.freeze
|
|
9
9
|
SOCIALIZE_URL = 'https://socialize.eu1.gigya.com'.freeze
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
# Lifetime of a JWT in seconds. A new one is fetched shortly before the
|
|
12
|
+
# old one expires.
|
|
13
|
+
JWT_LIFETIME = 900
|
|
14
|
+
JWT_MARGIN = 60
|
|
15
|
+
|
|
16
|
+
# logger is any object with #info
|
|
17
|
+
def initialize(email, password, device: Device.new, logger: nil)
|
|
12
18
|
raise ArgumentError unless email.is_a?(String) && password.is_a?(String)
|
|
13
19
|
|
|
14
20
|
@email = email
|
|
15
21
|
@password = password
|
|
16
22
|
@device = device
|
|
23
|
+
@logger = logger
|
|
17
24
|
end
|
|
18
25
|
attr_reader :email, :password, :device
|
|
19
26
|
|
|
@@ -25,13 +32,20 @@ module Ruze
|
|
|
25
32
|
end
|
|
26
33
|
|
|
27
34
|
def jwt
|
|
28
|
-
@jwt
|
|
35
|
+
return @jwt if @jwt && now < @jwt_expires_at
|
|
36
|
+
|
|
37
|
+
login_token = session_cookie_value
|
|
38
|
+
@logger&.info('Fetching new token')
|
|
39
|
+
fetched_at = now
|
|
40
|
+
@jwt = dig_from post(
|
|
29
41
|
"#{BASE_URL}/accounts.getJWT",
|
|
30
42
|
{ 'apiKey' => api_key,
|
|
31
|
-
'login_token' =>
|
|
43
|
+
'login_token' => login_token,
|
|
32
44
|
'fields' => 'data.personId,data.gigyaDataCenter',
|
|
33
|
-
'expiration' =>
|
|
45
|
+
'expiration' => JWT_LIFETIME }
|
|
34
46
|
), label: 'jwt', keys: %w[id_token]
|
|
47
|
+
@jwt_expires_at = fetched_at + JWT_LIFETIME - JWT_MARGIN
|
|
48
|
+
@jwt
|
|
35
49
|
end
|
|
36
50
|
|
|
37
51
|
# Logs in using the trusted device and returns the session cookie value.
|
|
@@ -88,6 +102,7 @@ module Ruze
|
|
|
88
102
|
private
|
|
89
103
|
|
|
90
104
|
def login(ids = device_ids)
|
|
105
|
+
@logger&.info('Logging in')
|
|
91
106
|
json = parse post(
|
|
92
107
|
"#{BASE_URL}/accounts.login",
|
|
93
108
|
login_params(ids), cookie: cookie(ids)
|
|
@@ -191,6 +206,10 @@ module Ruze
|
|
|
191
206
|
{ gmid: json['gmid'], ucid: json['ucid'] }
|
|
192
207
|
end
|
|
193
208
|
|
|
209
|
+
def now
|
|
210
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
211
|
+
end
|
|
212
|
+
|
|
194
213
|
def cookie(ids)
|
|
195
214
|
"gmid=#{ids[:gmid]}; ucid=#{ids[:ucid]}"
|
|
196
215
|
end
|
|
@@ -215,11 +234,16 @@ module Ruze
|
|
|
215
234
|
|
|
216
235
|
def dig_from(response, label:, keys:)
|
|
217
236
|
json = parse(response)
|
|
218
|
-
raise
|
|
237
|
+
raise error_class(json), "Error in #{label}: #{error_detail(json)}" unless json['errorCode']&.zero?
|
|
219
238
|
|
|
220
239
|
json.dig(*keys)
|
|
221
240
|
end
|
|
222
241
|
|
|
242
|
+
# The first three digits of a Gigya error code are the HTTP status
|
|
243
|
+
def error_class(json)
|
|
244
|
+
json['errorCode'].to_s.start_with?('401', '403') ? AuthenticationError : Error
|
|
245
|
+
end
|
|
246
|
+
|
|
223
247
|
def error_detail(json)
|
|
224
248
|
json['errorDetails'] || json['errorMessage'] || "errorCode #{json['errorCode']}"
|
|
225
249
|
end
|
data/lib/ruze/kamereon.rb
CHANGED
|
@@ -6,14 +6,19 @@ module Ruze
|
|
|
6
6
|
BASE_URL = 'https://api-wired-prod-1-euw1.wrd-aws.com/commerce/v1'.freeze
|
|
7
7
|
COUNTRY = 'DE'.freeze
|
|
8
8
|
|
|
9
|
+
# gigya_token is a String or a callable that returns a valid token
|
|
9
10
|
def initialize(person_id, gigya_token, vin = nil)
|
|
10
|
-
raise ArgumentError unless person_id.is_a?(String) && gigya_token.is_a?(String)
|
|
11
|
+
raise ArgumentError unless person_id.is_a?(String) && (gigya_token.is_a?(String) || gigya_token.respond_to?(:call))
|
|
11
12
|
|
|
12
13
|
@person_id = person_id
|
|
13
14
|
@gigya_token = gigya_token
|
|
14
15
|
@vin = vin
|
|
15
16
|
end
|
|
16
|
-
attr_reader :person_id
|
|
17
|
+
attr_reader :person_id
|
|
18
|
+
|
|
19
|
+
def gigya_token
|
|
20
|
+
@gigya_token.respond_to?(:call) ? @gigya_token.call : @gigya_token
|
|
21
|
+
end
|
|
17
22
|
|
|
18
23
|
def account_id
|
|
19
24
|
accounts.first['accountId']
|
|
@@ -62,6 +67,12 @@ module Ruze
|
|
|
62
67
|
{}
|
|
63
68
|
end
|
|
64
69
|
|
|
70
|
+
# Forgets the vehicle data, but keeps account and VIN
|
|
71
|
+
def reload
|
|
72
|
+
@battery = @cockpit = @location = nil
|
|
73
|
+
self
|
|
74
|
+
end
|
|
75
|
+
|
|
65
76
|
private
|
|
66
77
|
|
|
67
78
|
def api_key
|
|
@@ -90,7 +101,8 @@ module Ruze
|
|
|
90
101
|
def return_from(response, keys:)
|
|
91
102
|
unless response.is_a?(Net::HTTPOK)
|
|
92
103
|
caller = caller_locations(1, 1)[0].base_label
|
|
93
|
-
|
|
104
|
+
error = %w[401 403].include?(response.code) ? AuthenticationError : Error
|
|
105
|
+
raise error, "Error in #{caller}: #{response.message} (#{response.code})"
|
|
94
106
|
end
|
|
95
107
|
|
|
96
108
|
json = JSON.parse(response.body)
|
data/lib/ruze/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ruze
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Georg Ledermann
|
|
@@ -58,7 +58,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
58
58
|
- !ruby/object:Gem::Version
|
|
59
59
|
version: '0'
|
|
60
60
|
requirements: []
|
|
61
|
-
rubygems_version: 4.0.
|
|
61
|
+
rubygems_version: 4.0.21
|
|
62
62
|
specification_version: 4
|
|
63
63
|
summary: Unofficial Ruby client for the Renault ZE API
|
|
64
64
|
test_files: []
|