smile-identity-core 1.2.1 → 2.1.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/.github/workflows/release.yml +18 -0
- data/.github/workflows/test.yml +36 -0
- data/.gitignore +3 -0
- data/.rubocop.yml +6 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +75 -30
- data/Gemfile +3 -1
- data/Gemfile.lock +35 -7
- data/README.md +33 -464
- data/Rakefile +5 -3
- data/bin/console +4 -3
- data/examples/biometric_kyc.rb +67 -0
- data/examples/business_verification.rb +45 -0
- data/examples/document_verification.rb +74 -0
- data/examples/enhanced_kyc.rb +39 -0
- data/examples/example-project/Gemfile +7 -0
- data/examples/example-project/Gemfile.lock +25 -0
- data/examples/example-project/README.md +14 -0
- data/examples/example-project/sample.env +4 -0
- data/examples/example-project/smart_bank.rb +216 -0
- data/examples/smart_selfie_authentication.rb +56 -0
- data/lib/smile-identity-core/business_verification.rb +106 -0
- data/lib/smile-identity-core/constants/env.rb +13 -0
- data/lib/smile-identity-core/constants/image_type.rb +14 -0
- data/lib/smile-identity-core/constants/job_type.rb +13 -0
- data/lib/smile-identity-core/id_api.rb +41 -82
- data/lib/smile-identity-core/signature.rb +15 -36
- data/lib/smile-identity-core/utilities.rb +28 -55
- data/lib/smile-identity-core/validations.rb +32 -0
- data/lib/smile-identity-core/version.rb +4 -1
- data/lib/smile-identity-core/web_api.rb +87 -136
- data/lib/smile-identity-core.rb +11 -5
- data/smile-identity-core.gemspec +26 -21
- metadata +82 -22
- data/.travis.yml +0 -11
@@ -1,36 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'json'
|
2
4
|
require 'tempfile'
|
3
5
|
require 'base64'
|
4
6
|
require 'openssl'
|
5
7
|
require 'uri'
|
6
|
-
|
7
8
|
require 'typhoeus'
|
8
9
|
require 'zip'
|
9
10
|
|
10
11
|
module SmileIdentityCore
|
12
|
+
# Allows Identity verifications of ids with images
|
11
13
|
class WebApi
|
12
|
-
|
13
14
|
def initialize(partner_id, default_callback, api_key, sid_server)
|
14
15
|
@partner_id = partner_id.to_s
|
15
16
|
@callback_url = default_callback
|
16
17
|
@api_key = api_key
|
17
|
-
|
18
18
|
@sid_server = sid_server
|
19
|
-
if
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
@url = sid_server_mapping[sid_server.to_i]
|
25
|
-
else
|
26
|
-
@url = sid_server
|
27
|
-
end
|
19
|
+
@url = if sid_server !~ URI::DEFAULT_PARSER.make_regexp
|
20
|
+
SmileIdentityCore::ENV::SID_SERVER_MAPPING[sid_server.to_s]
|
21
|
+
else
|
22
|
+
sid_server
|
23
|
+
end
|
28
24
|
end
|
29
25
|
|
30
26
|
def submit_job(partner_params, images, id_info, options)
|
31
|
-
|
32
27
|
self.partner_params = symbolize_keys partner_params
|
33
|
-
|
28
|
+
|
29
|
+
if [JobType::ENHANCED_KYC, JobType::BUSINESS_VERIFICATION].include?(@partner_params[:job_type].to_i)
|
34
30
|
return SmileIdentityCore::IDApi.new(@partner_id, @api_key, @sid_server).submit_job(partner_params, id_info)
|
35
31
|
end
|
36
32
|
|
@@ -38,13 +34,9 @@ module SmileIdentityCore
|
|
38
34
|
self.id_info = symbolize_keys id_info
|
39
35
|
self.options = symbolize_keys options
|
40
36
|
|
41
|
-
|
42
|
-
@callback_url = @options[:optional_callback]
|
43
|
-
end
|
37
|
+
@callback_url = @options[:optional_callback] if @options[:optional_callback]&.length&.positive?
|
44
38
|
|
45
|
-
if @partner_params[:job_type].to_i == 1
|
46
|
-
validate_enroll_with_id
|
47
|
-
end
|
39
|
+
validate_enroll_with_id if @partner_params[:job_type].to_i == 1
|
48
40
|
|
49
41
|
validate_return_data
|
50
42
|
|
@@ -59,21 +51,18 @@ module SmileIdentityCore
|
|
59
51
|
job_id = partner_params[:job_id]
|
60
52
|
|
61
53
|
utilities = SmileIdentityCore::Utilities.new(@partner_id, @api_key, @sid_server)
|
62
|
-
utilities.get_job_status(user_id, job_id, options)
|
54
|
+
utilities.get_job_status(user_id, job_id, options)
|
63
55
|
end
|
64
56
|
|
65
57
|
def partner_params=(partner_params)
|
66
|
-
if partner_params
|
67
|
-
raise ArgumentError, 'Please ensure that you send through partner params'
|
68
|
-
end
|
58
|
+
raise ArgumentError, 'Please ensure that you send through partner params' if partner_params.nil?
|
69
59
|
|
70
|
-
|
71
|
-
raise ArgumentError, 'Partner params needs to be a hash'
|
72
|
-
end
|
60
|
+
raise ArgumentError, 'Partner params needs to be a hash' unless partner_params.is_a?(Hash)
|
73
61
|
|
74
|
-
[
|
75
|
-
|
76
|
-
raise ArgumentError,
|
62
|
+
%i[user_id job_id job_type].each do |key|
|
63
|
+
if partner_params[key].to_s.empty?
|
64
|
+
raise ArgumentError,
|
65
|
+
"Please make sure that #{key} is included in the partner params"
|
77
66
|
end
|
78
67
|
end
|
79
68
|
|
@@ -81,46 +70,30 @@ module SmileIdentityCore
|
|
81
70
|
end
|
82
71
|
|
83
72
|
def images=(images)
|
84
|
-
if images
|
85
|
-
raise ArgumentError, 'Please ensure that you send through image details'
|
86
|
-
end
|
73
|
+
raise ArgumentError, 'Please ensure that you send through image details' if images.nil?
|
87
74
|
|
88
|
-
|
89
|
-
raise ArgumentError, 'Image details needs to be an array'
|
90
|
-
end
|
75
|
+
raise ArgumentError, 'Image details needs to be an array' unless images.is_a?(Array)
|
91
76
|
|
92
77
|
# all job types require atleast a selfie
|
93
|
-
if images.length
|
78
|
+
if images.length.zero? || images.none? { |h| (h[:image_type_id]).zero? || h[:image_type_id] == 2 }
|
94
79
|
raise ArgumentError, 'You need to send through at least one selfie image'
|
95
80
|
end
|
96
81
|
|
97
82
|
@images = images.map { |image| symbolize_keys image }
|
98
|
-
|
99
83
|
end
|
100
84
|
|
101
85
|
def id_info=(id_info)
|
102
|
-
|
103
|
-
updated_id_info = id_info
|
104
|
-
|
105
|
-
if updated_id_info.nil?
|
106
|
-
updated_id_info = {}
|
107
|
-
end
|
86
|
+
updated_id_info = id_info.nil? ? {} : id_info
|
108
87
|
|
109
88
|
# if it doesnt exist, set it false
|
110
|
-
if
|
111
|
-
updated_id_info[:entered] = "false"
|
112
|
-
end
|
89
|
+
updated_id_info[:entered] = 'false' if !updated_id_info.key?(:entered) || id_info[:entered].empty?
|
113
90
|
|
114
91
|
# if it's a boolean
|
115
|
-
if
|
116
|
-
updated_id_info[:entered] = id_info[:entered].to_s
|
117
|
-
end
|
92
|
+
updated_id_info[:entered] = id_info[:entered].to_s if !updated_id_info[:entered].nil? == updated_id_info[:entered]
|
118
93
|
|
119
94
|
if updated_id_info[:entered] && updated_id_info[:entered] == 'true'
|
120
|
-
[
|
121
|
-
|
122
|
-
raise ArgumentError, "Please make sure that #{key.to_s} is included in the id_info"
|
123
|
-
end
|
95
|
+
%i[country id_type id_number].each do |key|
|
96
|
+
raise ArgumentError, "Please make sure that #{key} is included in the id_info" if id_info[key].to_s.empty?
|
124
97
|
end
|
125
98
|
end
|
126
99
|
|
@@ -134,7 +107,6 @@ module SmileIdentityCore
|
|
134
107
|
updated_options[:return_job_status] = check_boolean(:return_job_status, options)
|
135
108
|
updated_options[:return_image_links] = check_boolean(:return_image_links, options)
|
136
109
|
updated_options[:return_history] = check_boolean(:return_history, options)
|
137
|
-
@use_new_signature = updated_options.fetch(:signature, false)
|
138
110
|
|
139
111
|
@options = updated_options
|
140
112
|
end
|
@@ -157,7 +129,13 @@ module SmileIdentityCore
|
|
157
129
|
private
|
158
130
|
|
159
131
|
def request_web_token(request_params)
|
160
|
-
request_params
|
132
|
+
request_params
|
133
|
+
.merge(SmileIdentityCore::Signature.new(@partner_id, @api_key).generate_signature(Time.now.to_s))
|
134
|
+
.merge!(
|
135
|
+
{ partner_id: @partner_id,
|
136
|
+
source_sdk: SmileIdentityCore::SOURCE_SDK,
|
137
|
+
source_sdk_version: SmileIdentityCore::VERSION }
|
138
|
+
)
|
161
139
|
url = "#{@url}/token"
|
162
140
|
|
163
141
|
response = Typhoeus.post(
|
@@ -171,8 +149,8 @@ module SmileIdentityCore
|
|
171
149
|
raise "#{response.code}: #{response.body}"
|
172
150
|
end
|
173
151
|
|
174
|
-
def symbolize_keys
|
175
|
-
|
152
|
+
def symbolize_keys(params)
|
153
|
+
params.is_a?(Hash) ? params.transform_keys(&:to_sym) : params
|
176
154
|
end
|
177
155
|
|
178
156
|
def validate_return_data
|
@@ -182,25 +160,21 @@ module SmileIdentityCore
|
|
182
160
|
end
|
183
161
|
|
184
162
|
def validate_enroll_with_id
|
185
|
-
if(
|
163
|
+
if (@images.none? { |h| h[:image_type_id] == 1 || h[:image_type_id] == 3 }) && @id_info[:entered] != 'true'
|
186
164
|
raise ArgumentError, 'You are attempting to complete a job type 1 without providing an id card image or id info'
|
187
165
|
end
|
188
166
|
end
|
189
167
|
|
190
168
|
def check_boolean(key, obj)
|
191
|
-
if
|
192
|
-
return false
|
193
|
-
end
|
169
|
+
return false if !obj || !obj[key]
|
194
170
|
|
195
|
-
if
|
196
|
-
raise ArgumentError, "#{key} needs to be a boolean"
|
197
|
-
end
|
171
|
+
raise ArgumentError, "#{key} needs to be a boolean" if !obj[key].nil? != obj[key]
|
198
172
|
|
199
173
|
obj[key]
|
200
174
|
end
|
201
175
|
|
202
176
|
def check_string(key, obj)
|
203
|
-
if
|
177
|
+
if !obj || !obj[key]
|
204
178
|
''
|
205
179
|
else
|
206
180
|
obj[key]
|
@@ -217,55 +191,37 @@ module SmileIdentityCore
|
|
217
191
|
keys.select { |key| blank?(obj, key) }
|
218
192
|
end
|
219
193
|
|
220
|
-
def request_security(use_new_signature: true)
|
221
|
-
if use_new_signature
|
222
|
-
@timestamp = Time.now.to_s
|
223
|
-
{
|
224
|
-
signature: SmileIdentityCore::Signature.new(@partner_id, @api_key).generate_signature(@timestamp)[:signature],
|
225
|
-
timestamp: @timestamp,
|
226
|
-
}
|
227
|
-
else
|
228
|
-
@timestamp = Time.now.to_i
|
229
|
-
{
|
230
|
-
sec_key: SmileIdentityCore::Signature.new(@partner_id, @api_key).generate_sec_key(@timestamp)[:sec_key],
|
231
|
-
timestamp: @timestamp,
|
232
|
-
}
|
233
|
-
end
|
234
|
-
end
|
235
|
-
|
236
194
|
def configure_prep_upload_json
|
237
|
-
|
195
|
+
SmileIdentityCore::Signature.new(@partner_id, @api_key).generate_signature(Time.now.to_s).merge(
|
238
196
|
file_name: 'selfie.zip',
|
239
197
|
smile_client_id: @partner_id,
|
240
198
|
partner_params: @partner_params,
|
241
199
|
model_parameters: {}, # what is this for
|
242
|
-
callback_url: @callback_url
|
200
|
+
callback_url: @callback_url,
|
201
|
+
source_sdk: SmileIdentityCore::SOURCE_SDK,
|
202
|
+
source_sdk_version: SmileIdentityCore::VERSION
|
243
203
|
).to_json
|
244
204
|
end
|
245
205
|
|
246
206
|
def setup_requests
|
247
|
-
|
248
207
|
url = "#{@url}/upload"
|
249
208
|
request = Typhoeus::Request.new(
|
250
209
|
url,
|
251
210
|
method: 'POST',
|
252
|
-
headers: {'Content-Type'=>
|
211
|
+
headers: { 'Content-Type' => 'application/json' },
|
253
212
|
body: configure_prep_upload_json
|
254
213
|
)
|
255
214
|
|
256
215
|
request.on_complete do |response|
|
257
216
|
if response.success?
|
258
217
|
# TODO: if/when we sign these responses, verify the signature here and raise if it's off.
|
259
|
-
# if updated_options[:signature]
|
260
218
|
# SmileIdentityCore::Signature.new(@partner_id, @api_key).generate_signature(@timestamp)
|
261
|
-
# else
|
262
|
-
# SmileIdentityCore::Signature.new(@partner_id, @api_key).generate_sec_key(@timestamp)
|
263
|
-
# end
|
264
219
|
|
265
220
|
prep_upload_response = JSON.parse(response.body)
|
266
221
|
info_json = configure_info_json(prep_upload_response)
|
267
222
|
|
268
|
-
file_upload_response = upload_file(prep_upload_response['upload_url'], info_json,
|
223
|
+
file_upload_response = upload_file(prep_upload_response['upload_url'], info_json,
|
224
|
+
prep_upload_response['smile_job_id'])
|
269
225
|
return file_upload_response
|
270
226
|
end
|
271
227
|
|
@@ -275,43 +231,44 @@ module SmileIdentityCore
|
|
275
231
|
end
|
276
232
|
|
277
233
|
def configure_info_json(server_information)
|
278
|
-
|
234
|
+
{
|
279
235
|
"package_information": {
|
280
236
|
"apiVersion": {
|
281
237
|
"buildNumber": 0,
|
282
238
|
"majorVersion": 2,
|
283
239
|
"minorVersion": 0
|
284
240
|
},
|
285
|
-
"language":
|
241
|
+
"language": 'ruby'
|
286
242
|
},
|
287
|
-
"misc_information":
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
243
|
+
"misc_information": SmileIdentityCore::Signature.new(@partner_id, @api_key)
|
244
|
+
.generate_signature(Time.now.to_s)
|
245
|
+
.merge(
|
246
|
+
"retry": 'false',
|
247
|
+
"partner_params": @partner_params,
|
248
|
+
"file_name": 'selfie.zip', # figure out what to do here
|
249
|
+
"smile_client_id": @partner_id,
|
250
|
+
"callback_url": @callback_url,
|
251
|
+
"userData": { # TO ASK what goes here
|
252
|
+
"isVerifiedProcess": false,
|
253
|
+
"name": '',
|
254
|
+
"fbUserID": '',
|
255
|
+
"firstName": 'Bill',
|
256
|
+
"lastName": '',
|
257
|
+
"gender": '',
|
258
|
+
"email": '',
|
259
|
+
"phone": '',
|
260
|
+
"countryCode": '+',
|
261
|
+
"countryName": ''
|
262
|
+
}
|
263
|
+
),
|
306
264
|
"id_info": @id_info,
|
307
265
|
"images": configure_image_payload,
|
308
266
|
"server_information": server_information
|
309
267
|
}
|
310
|
-
info
|
311
268
|
end
|
312
269
|
|
313
270
|
def configure_image_payload
|
314
|
-
@images.map
|
271
|
+
@images.map do |i|
|
315
272
|
if image_file?(i[:image_type_id])
|
316
273
|
{
|
317
274
|
image_type_id: i[:image_type_id],
|
@@ -325,11 +282,11 @@ module SmileIdentityCore
|
|
325
282
|
file_name: ''
|
326
283
|
}
|
327
284
|
end
|
328
|
-
|
285
|
+
end
|
329
286
|
end
|
330
287
|
|
331
288
|
def image_file?(type)
|
332
|
-
type.to_i
|
289
|
+
type.to_i.zero? || type.to_i == 1
|
333
290
|
end
|
334
291
|
|
335
292
|
def zip_up_file(info_json)
|
@@ -338,9 +295,9 @@ module SmileIdentityCore
|
|
338
295
|
zos.put_next_entry('info.json')
|
339
296
|
zos.puts JSON.pretty_generate(info_json)
|
340
297
|
|
341
|
-
if @images.length
|
298
|
+
if @images.length.positive?
|
342
299
|
@images.each do |img|
|
343
|
-
if img[:image_type_id]
|
300
|
+
if (img[:image_type_id]).zero? || img[:image_type_id] == 1
|
344
301
|
zos.put_next_entry(File.basename(img[:image]))
|
345
302
|
zos.print IO.read(img[:image])
|
346
303
|
end
|
@@ -350,36 +307,31 @@ module SmileIdentityCore
|
|
350
307
|
end
|
351
308
|
|
352
309
|
def upload_file(url, info_json, smile_job_id)
|
353
|
-
|
354
310
|
file = zip_up_file(info_json)
|
355
311
|
file.rewind
|
356
312
|
|
357
313
|
request = Typhoeus::Request.new(
|
358
314
|
url,
|
359
315
|
method: 'PUT',
|
360
|
-
headers: {'Content-Type'=>
|
361
|
-
body: file.read
|
316
|
+
headers: { 'Content-Type' => 'application/zip' },
|
317
|
+
body: file.read
|
362
318
|
)
|
363
319
|
|
364
320
|
request.on_complete do |response|
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
end
|
375
|
-
end
|
376
|
-
raise " #{response.code}: #{response.body}"
|
321
|
+
raise " #{response.code}: #{response.body}" unless response.success?
|
322
|
+
|
323
|
+
return { success: true, smile_job_id: smile_job_id }.to_json unless @options[:return_job_status]
|
324
|
+
|
325
|
+
@utilies_connection = SmileIdentityCore::Utilities.new(@partner_id, @api_key, @sid_server)
|
326
|
+
job_response = query_job_status
|
327
|
+
job_response['success'] = true
|
328
|
+
job_response['smile_job_id'] = smile_job_id
|
329
|
+
return job_response
|
377
330
|
end
|
378
331
|
request.run
|
379
|
-
|
380
332
|
end
|
381
333
|
|
382
|
-
def query_job_status(counter=0)
|
334
|
+
def query_job_status(counter = 0)
|
383
335
|
counter < 4 ? (sleep 2) : (sleep 6)
|
384
336
|
counter += 1
|
385
337
|
|
@@ -390,7 +342,6 @@ module SmileIdentityCore
|
|
390
342
|
else
|
391
343
|
query_job_status(counter)
|
392
344
|
end
|
393
|
-
|
394
345
|
end
|
395
346
|
end
|
396
347
|
end
|
data/lib/smile-identity-core.rb
CHANGED
@@ -1,8 +1,14 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'smile-identity-core/version'
|
4
|
+
require 'smile-identity-core/web_api'
|
5
|
+
require 'smile-identity-core/id_api'
|
6
|
+
require 'smile-identity-core/signature'
|
7
|
+
require 'smile-identity-core/utilities'
|
8
|
+
require 'smile-identity-core/business_verification'
|
9
|
+
require 'smile-identity-core/constants/env'
|
10
|
+
require 'smile-identity-core/constants/image_type'
|
11
|
+
require 'smile-identity-core/constants/job_type'
|
6
12
|
|
7
13
|
module SmileIdentityCore
|
8
14
|
class Error < StandardError; end
|
data/smile-identity-core.gemspec
CHANGED
@@ -1,39 +1,44 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
2
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
-
require
|
5
|
+
require 'smile-identity-core/version'
|
4
6
|
|
5
7
|
Gem::Specification.new do |spec|
|
6
|
-
spec.name =
|
8
|
+
spec.name = 'smile-identity-core'
|
7
9
|
spec.version = SmileIdentityCore::VERSION
|
8
|
-
spec.authors = [
|
9
|
-
spec.email = [
|
10
|
+
spec.authors = ['Smile Identity']
|
11
|
+
spec.email = ['support@smileidentity.com']
|
10
12
|
|
11
|
-
spec.summary =
|
12
|
-
|
13
|
-
spec.
|
14
|
-
spec.
|
13
|
+
spec.summary = 'The Smile Identity Web API allows the user to access\
|
14
|
+
most of the features of the Smile Identity system through direct server to server queries.'
|
15
|
+
spec.description = 'The Official Smile Identity gem'
|
16
|
+
spec.homepage = 'https://www.smileidentity.com/'
|
17
|
+
spec.required_ruby_version = '>= 2.5'
|
15
18
|
spec.license = 'MIT'
|
16
19
|
|
17
|
-
spec.metadata[
|
18
|
-
spec.metadata[
|
19
|
-
spec.metadata[
|
20
|
-
spec.metadata[
|
20
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
21
|
+
spec.metadata['source_code_uri'] = 'https://github.com/smileidentity/smile-identity-core-ruby'
|
22
|
+
spec.metadata['documentation_uri'] = 'https://docs.smileidentity.com'
|
23
|
+
spec.metadata['changelog_uri'] = 'https://github.com/smileidentity/smile-identity-core-ruby/blob/master/CHANGELOG.md'
|
21
24
|
|
22
25
|
# Specify which files should be added to the gem when it is released.
|
23
26
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
24
|
-
spec.files
|
27
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
25
28
|
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
26
29
|
end
|
27
|
-
spec.bindir =
|
30
|
+
spec.bindir = 'exe'
|
28
31
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
29
|
-
spec.require_paths = [
|
32
|
+
spec.require_paths = ['lib']
|
30
33
|
|
31
|
-
spec.add_development_dependency
|
32
|
-
spec.add_development_dependency
|
33
|
-
spec.add_development_dependency
|
34
|
+
spec.add_development_dependency 'bundler', '~> 2.0'
|
35
|
+
spec.add_development_dependency 'rake', '~> 12.3'
|
36
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
37
|
+
spec.add_development_dependency 'rubocop', '~> 1.37.1'
|
38
|
+
spec.add_development_dependency 'rubocop-rake', '~> 0.6.0'
|
39
|
+
spec.add_development_dependency 'rubocop-rspec', '~> 2.14.1'
|
34
40
|
spec.add_development_dependency 'simplecov', '~> 0.12.0'
|
35
41
|
|
36
|
-
spec.add_dependency 'typhoeus', '~> 1.0', '>= 1.0.1'
|
37
42
|
spec.add_dependency 'rubyzip', '~> 1.2', '>= 1.2.3'
|
38
|
-
|
43
|
+
spec.add_dependency 'typhoeus', '~> 1.0', '>= 1.0.1'
|
39
44
|
end
|