hypertrack_v3 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/lib/hypertrack_v3.rb +262 -0
- metadata +72 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 74901d0ab99d98deb111dd1eb7352c51de27b953af93dc3bd9d839450fa8688e
|
4
|
+
data.tar.gz: bc8660120b533585ffedd5427709338d63c21d0db0a3587eedcf120a8e79ff8f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 7cdfb4528c8ebcdbbf53b1397b75f2235e2d9019ddb126a863d847b44bb8c0c5a50e4e74c792c1dc9dbf94b97b51e520c2d89073f17937058feb7da4e50ebd6a
|
7
|
+
data.tar.gz: 90426a3d4a25d3f9ac401dadd05fcdaade615c1c88ab5220996d0b4b6f1602b3c378c6047078b0ec534b9d8bad89551ff2c49a7149d534f1597169c8b10ae8ff
|
@@ -0,0 +1,262 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class HypertrackV3
|
4
|
+
BASE_URL='https://v3.api.hypertrack.com'
|
5
|
+
|
6
|
+
RES_DEVICES = '/devices'
|
7
|
+
RES_DEVICE = "/devices/%{device_id}"
|
8
|
+
RES_TRIPS = "/trips"
|
9
|
+
RES_TRIP = "/trips/%{trip_id}"
|
10
|
+
RES_TRIP_COMPLETE = "/trips/%{trip_id}/complete"
|
11
|
+
|
12
|
+
class HttpError < StandardError
|
13
|
+
attr_reader :code
|
14
|
+
attr_reader :message
|
15
|
+
|
16
|
+
def initialize(code, message)
|
17
|
+
@code = code
|
18
|
+
@message = message
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class InternalServerError < HttpError; end
|
23
|
+
class ClientError < HttpError; end
|
24
|
+
|
25
|
+
class RegisterHookError < StandardError; end
|
26
|
+
|
27
|
+
def initialize(account_id, secret_key)
|
28
|
+
@client = nil
|
29
|
+
@account_id = account_id
|
30
|
+
@secret_key = secret_key
|
31
|
+
end
|
32
|
+
|
33
|
+
def client
|
34
|
+
@client ||= Faraday.new url: self.class::BASE_URL do |conn|
|
35
|
+
conn.basic_auth(@account_id, @secret_key)
|
36
|
+
conn.request :json
|
37
|
+
conn.response :json, :content_type => /\bjson$/
|
38
|
+
conn.response :json, :parser_options => { :object_class => OpenStruct }
|
39
|
+
conn.use Faraday::Response::Logger, HypertrackV3.logger, bodies: true
|
40
|
+
conn.use :instrumentation
|
41
|
+
conn.adapter Faraday.default_adapter
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def parse(res)
|
46
|
+
raise InternalServerError.new(res.status, res.body) if res.status >= 500
|
47
|
+
raise ClientError.new(res.status, res.body) if res.status >= 400
|
48
|
+
raise HttpError.new(res.status, res.body) unless res.success?
|
49
|
+
res.body
|
50
|
+
end
|
51
|
+
|
52
|
+
def device_list
|
53
|
+
parse(client.get(self.class::RES_DEVICES))
|
54
|
+
end
|
55
|
+
|
56
|
+
def device_get(id:, **)
|
57
|
+
parse(client.get(self.class::RES_DEVICE % {device_id: id}))
|
58
|
+
end
|
59
|
+
|
60
|
+
def device_del(id:, **)
|
61
|
+
parse(client.delete(self.class::RES_DEVICE % {device_id: id}))
|
62
|
+
end
|
63
|
+
|
64
|
+
def trip_create(
|
65
|
+
device_id:,
|
66
|
+
destination:,
|
67
|
+
geofences:,
|
68
|
+
metadata:, **)
|
69
|
+
parse(
|
70
|
+
client.post(self.class::RES_TRIPS) do |req|
|
71
|
+
req.body = {
|
72
|
+
device_id: device_id,
|
73
|
+
destination: destination,
|
74
|
+
geofences: geofences,
|
75
|
+
metadata: metadata,
|
76
|
+
}
|
77
|
+
end
|
78
|
+
)
|
79
|
+
end
|
80
|
+
|
81
|
+
def trip_list(limit=50, offset=0)
|
82
|
+
parse(client.get(self.class::RES_TRIPS, params={limit: limit, offset: offset}))
|
83
|
+
end
|
84
|
+
|
85
|
+
def trip_get(id:, **)
|
86
|
+
parse(client.get(self.class::RES_TRIP % {trip_id: id}))
|
87
|
+
end
|
88
|
+
|
89
|
+
def trip_set_complete(id:, **)
|
90
|
+
parse(client.post(self.class::RES_TRIP_COMPLETE % {trip_id: id}))
|
91
|
+
end
|
92
|
+
|
93
|
+
def self.logger
|
94
|
+
@@logger ||= defined?(Rails) ? Rails.logger : Logger.new(STDOUT)
|
95
|
+
end
|
96
|
+
|
97
|
+
def self.logger=(logger)
|
98
|
+
@@logger = logger
|
99
|
+
end
|
100
|
+
|
101
|
+
def self.error_handler
|
102
|
+
@@error_handler ||= ->(*, **) { nil }
|
103
|
+
end
|
104
|
+
|
105
|
+
def self.error_handler=(error_handler)
|
106
|
+
@@error_handler = error_handler
|
107
|
+
end
|
108
|
+
|
109
|
+
def self.exception_handler
|
110
|
+
@@error_handler ||= ->(*, **) { nil }
|
111
|
+
end
|
112
|
+
|
113
|
+
def self.exception_handler=(error_handler)
|
114
|
+
@@error_handler = error_handler
|
115
|
+
end
|
116
|
+
|
117
|
+
def self.log_error(message, **data)
|
118
|
+
self.logger.error({message: message}.merge(data))
|
119
|
+
self.error_handler.(message, **data)
|
120
|
+
end
|
121
|
+
|
122
|
+
def self.log_exception(exception, **data)
|
123
|
+
self.logger.error({exception: exception.as_json}.merge(data))
|
124
|
+
self.exception_handler.(exception, **data)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
if defined? Rails
|
129
|
+
class HypertrackV3::Engine < Rails::Engine
|
130
|
+
class WebhookParser
|
131
|
+
class LogHook
|
132
|
+
def self.call(type, device_id, data, created_at, recorded_at)
|
133
|
+
HypertrackV3.logger.debug("HypertrackV3::LogHook#{type}: #{device_id} -> #{data}")
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def self.client
|
138
|
+
@@client ||= Faraday.new do |conn|
|
139
|
+
conn.use Faraday::Response::Logger, HypertrackV3.logger, bodies: true
|
140
|
+
conn.use :instrumentation
|
141
|
+
conn.adapter Faraday.default_adapter
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
@@client = nil
|
146
|
+
|
147
|
+
# Sets up default hooks
|
148
|
+
@@hooks = {
|
149
|
+
location: ->(*args) { LogHook.('Location', *args) },
|
150
|
+
device_status: ->(*args) { LogHook.('DeviceStatus', *args) },
|
151
|
+
battery: ->(*args) { LogHook.('Battery', *args) },
|
152
|
+
trip: ->(*args) { LogHook.('Trip', *args) },
|
153
|
+
}
|
154
|
+
|
155
|
+
def self.register_with_hypertrack(request)
|
156
|
+
register_data = JSON.parse(request.body)
|
157
|
+
|
158
|
+
resp = self.client.get register_data["SubscribeURL"]
|
159
|
+
data = Nokogiri::XML(resp.body)
|
160
|
+
data.remove_namespaces!
|
161
|
+
token = data.at_xpath('//SubscriptionArn')&.content
|
162
|
+
return self.serve(400, {error: 'SubscriptionArn not found'}) if token.empty?
|
163
|
+
Rails.cache.write('/hypertrack_v3/subscription_arn', token, expires_in: 100.years)
|
164
|
+
|
165
|
+
self.serve(200)
|
166
|
+
end
|
167
|
+
|
168
|
+
def self.dispatch(request)
|
169
|
+
if (cached = Rails.cache.fetch('/hypertrack_v3/subscription_arn')) != request.headers['x_amz_sns_subscription_arn']
|
170
|
+
HypertrackV3.log_error(
|
171
|
+
"invalid subscription-arn header",
|
172
|
+
{
|
173
|
+
subscription_arn: {
|
174
|
+
request: request.header,
|
175
|
+
cache: cached,
|
176
|
+
}
|
177
|
+
}.to_json
|
178
|
+
)
|
179
|
+
return self.serve(400, {error: "invalid subscription-arn header"})
|
180
|
+
end
|
181
|
+
if (cached = Rails.cache.fetch("/hypertrack_v3/#{request.headers['x_amz_sns_message_id']}")).present?
|
182
|
+
HypertrackV3.log_error(
|
183
|
+
"Message Id already seen",
|
184
|
+
{
|
185
|
+
sns_message_id: {
|
186
|
+
request: request.headers['x_amz_sns_message_id'],
|
187
|
+
cache: cached,
|
188
|
+
}
|
189
|
+
}
|
190
|
+
)
|
191
|
+
return self.serve(400)
|
192
|
+
end
|
193
|
+
Rails.cache.write(
|
194
|
+
"/hypertrack_v3/#{request.headers['x_amz_sns_message_id']}", true,
|
195
|
+
expires_in: 1.hour
|
196
|
+
)
|
197
|
+
|
198
|
+
begin
|
199
|
+
data = JSON.parse(request.body, object_class: OpenStruct)
|
200
|
+
rescue JSON::ParserError => err
|
201
|
+
HypertrackV3.log_exception(err)
|
202
|
+
end
|
203
|
+
|
204
|
+
res = true
|
205
|
+
data.each do |datum|
|
206
|
+
if @@hooks.include? datum.type.to_sym
|
207
|
+
res &= @@hooks[datum.type.to_sym].(datum.device_id, datum.data, datum.created_at, datum.recorded_at)
|
208
|
+
else
|
209
|
+
res = false
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
self.serve(res ? 200 : 400)
|
214
|
+
end
|
215
|
+
|
216
|
+
def self.register_hook(name, callable)
|
217
|
+
raise RegisterHookError("Invalid name argument: #{name}") unless @@hooks.keys.include? name.to_sym
|
218
|
+
raise RegisterHookError("Invalid callable argument: #{callable}") unless callable.respond_to? :call
|
219
|
+
@@hooks[name.to_sym] = callable
|
220
|
+
end
|
221
|
+
|
222
|
+
def self.call(env)
|
223
|
+
request = parse_request(env)
|
224
|
+
|
225
|
+
case request.headers['x_amz_sns_message_type']
|
226
|
+
when 'SubscriptionConfirmation'
|
227
|
+
self.register_with_hypertrack request
|
228
|
+
when 'Notification'
|
229
|
+
self.dispatch request
|
230
|
+
else
|
231
|
+
HypertrackV3.log_error(
|
232
|
+
"invalid message-type header",
|
233
|
+
{
|
234
|
+
message_type: request.header['x_amz_sns_messate_type'],
|
235
|
+
}.to_json
|
236
|
+
)
|
237
|
+
self.serve(400, {error: "invalid message-type header"})
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
def self.parse_request(env)
|
242
|
+
OpenStruct.new({
|
243
|
+
headers: env.select {|k,v| k.to_s.start_with? 'HTTP_'}
|
244
|
+
.collect {|key, val| [key.to_s.sub(/^HTTP_/, '').downcase, val]}.to_h,
|
245
|
+
params: env['rack.request.query_hash'],
|
246
|
+
body: env['rack.input']&.read
|
247
|
+
})
|
248
|
+
end
|
249
|
+
|
250
|
+
def self.serve(code, params={})
|
251
|
+
[
|
252
|
+
code,
|
253
|
+
{"Content-Type" => "application/json; charset=utf-8"},
|
254
|
+
[params.to_json]
|
255
|
+
]
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
endpoint WebhookParser
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
metadata
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: hypertrack_v3
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Bernard Pratz
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2019-10-29 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: faraday
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.15.4
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.15.4
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: nokogiri
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.8'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.8'
|
41
|
+
description: Ruby wrapper around HyperTrack's API V3. Refer http://docs.hypertrack.com/
|
42
|
+
for more information.
|
43
|
+
email: guyzmo+pub@m0g.net
|
44
|
+
executables: []
|
45
|
+
extensions: []
|
46
|
+
extra_rdoc_files: []
|
47
|
+
files:
|
48
|
+
- lib/hypertrack_v3.rb
|
49
|
+
homepage: http://rubygems.org/gems/hypertrack_v3
|
50
|
+
licenses:
|
51
|
+
- LGPL-3.0-only
|
52
|
+
metadata: {}
|
53
|
+
post_install_message:
|
54
|
+
rdoc_options: []
|
55
|
+
require_paths:
|
56
|
+
- lib
|
57
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 2.0.0
|
62
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
63
|
+
requirements:
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: '0'
|
67
|
+
requirements: []
|
68
|
+
rubygems_version: 3.0.6
|
69
|
+
signing_key:
|
70
|
+
specification_version: 4
|
71
|
+
summary: Ruby bindings for the HyperTrack V3 API!
|
72
|
+
test_files: []
|