identisure-client 0.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.
@@ -0,0 +1,224 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Identisure
4
+ module Freja
5
+ class AuthRequest
6
+ attr_reader :ssn, :callback_url, :min_registration_level, :org_number,
7
+ :request_address, :ref_id
8
+
9
+ def initialize(ssn: nil, callback_url: nil, min_registration_level: nil,
10
+ org_number: nil, request_address: nil, ref_id: nil)
11
+ @ssn = ssn; @callback_url = callback_url
12
+ @min_registration_level = min_registration_level; @org_number = org_number
13
+ @request_address = request_address; @ref_id = ref_id
14
+ end
15
+
16
+ def to_h
17
+ {
18
+ ssn: @ssn, callbackUrl: @callback_url,
19
+ minRegistrationLevel: @min_registration_level, orgNumber: @org_number,
20
+ requestAddress: @request_address, refId: @ref_id
21
+ }.compact
22
+ end
23
+ end
24
+
25
+ class BackchannelAuthRequest
26
+ attr_reader :ssn, :country, :callback_url, :min_registration_level,
27
+ :org_number, :request_address, :ref_id
28
+
29
+ def initialize(ssn:, country:, callback_url: nil, min_registration_level: nil,
30
+ org_number: nil, request_address: nil, ref_id: nil)
31
+ @ssn = ssn; @country = country; @callback_url = callback_url
32
+ @min_registration_level = min_registration_level; @org_number = org_number
33
+ @request_address = request_address; @ref_id = ref_id
34
+ end
35
+
36
+ def to_h
37
+ {
38
+ ssn: @ssn, country: @country, callbackUrl: @callback_url,
39
+ minRegistrationLevel: @min_registration_level, orgNumber: @org_number,
40
+ requestAddress: @request_address, refId: @ref_id
41
+ }.compact
42
+ end
43
+ end
44
+
45
+ class SignRequest
46
+ attr_reader :text, :ssn, :callback_url, :min_registration_level,
47
+ :org_number, :request_address, :ref_id
48
+
49
+ def initialize(text:, ssn: nil, callback_url: nil, min_registration_level: nil,
50
+ org_number: nil, request_address: nil, ref_id: nil)
51
+ @text = text; @ssn = ssn; @callback_url = callback_url
52
+ @min_registration_level = min_registration_level; @org_number = org_number
53
+ @request_address = request_address; @ref_id = ref_id
54
+ end
55
+
56
+ def to_h
57
+ {
58
+ text: @text, ssn: @ssn, callbackUrl: @callback_url,
59
+ minRegistrationLevel: @min_registration_level, orgNumber: @org_number,
60
+ requestAddress: @request_address, refId: @ref_id
61
+ }.compact
62
+ end
63
+ end
64
+
65
+ class BackchannelSignRequest
66
+ attr_reader :ssn, :country, :text, :callback_url, :min_registration_level,
67
+ :org_number, :request_address, :ref_id
68
+
69
+ def initialize(ssn:, country:, text:, callback_url: nil, min_registration_level: nil,
70
+ org_number: nil, request_address: nil, ref_id: nil)
71
+ @ssn = ssn; @country = country; @text = text; @callback_url = callback_url
72
+ @min_registration_level = min_registration_level; @org_number = org_number
73
+ @request_address = request_address; @ref_id = ref_id
74
+ end
75
+
76
+ def to_h
77
+ {
78
+ ssn: @ssn, country: @country, text: @text, callbackUrl: @callback_url,
79
+ minRegistrationLevel: @min_registration_level, orgNumber: @org_number,
80
+ requestAddress: @request_address, refId: @ref_id
81
+ }.compact
82
+ end
83
+ end
84
+
85
+ class Pending
86
+ attr_reader :id, :ref_id, :auto_start_token, :qr_data
87
+
88
+ def initialize(id:, auto_start_token:, qr_data:, ref_id: nil)
89
+ @id = id; @ref_id = ref_id
90
+ @auto_start_token = auto_start_token; @qr_data = qr_data
91
+ end
92
+
93
+ def deconstruct_keys(_)
94
+ { id: @id, ref_id: @ref_id, auto_start_token: @auto_start_token, qr_data: @qr_data }
95
+ end
96
+
97
+ def self.from_json(d)
98
+ new(id: d['id'], ref_id: d['refId'],
99
+ auto_start_token: d['autoStartToken'], qr_data: d['qrData'])
100
+ end
101
+ end
102
+
103
+ class Completed
104
+ attr_reader :id, :ref_id, :ssn, :country, :name, :given_name, :surname,
105
+ :address, :company_signatory_text, :authenticated_at, :signed_at
106
+
107
+ def initialize(id:, ssn:, country:, name:, given_name:, surname:,
108
+ ref_id: nil, address: nil, company_signatory_text: nil,
109
+ authenticated_at: nil, signed_at: nil)
110
+ @id = id; @ref_id = ref_id; @ssn = ssn; @country = country
111
+ @name = name; @given_name = given_name; @surname = surname
112
+ @address = address; @company_signatory_text = company_signatory_text
113
+ @authenticated_at = authenticated_at; @signed_at = signed_at
114
+ end
115
+
116
+ def deconstruct_keys(_)
117
+ { id: @id, ref_id: @ref_id, ssn: @ssn, country: @country,
118
+ name: @name, given_name: @given_name, surname: @surname,
119
+ address: @address, company_signatory_text: @company_signatory_text,
120
+ authenticated_at: @authenticated_at, signed_at: @signed_at }
121
+ end
122
+
123
+ def self.from_json(d)
124
+ new(
125
+ id: d['id'], ref_id: d['refId'], ssn: d['ssn'], country: d['country'],
126
+ name: d['name'], given_name: d['givenName'], surname: d['surname'],
127
+ address: d['address'], company_signatory_text: d['companySignatoryText'],
128
+ authenticated_at: d['authenticatedAt'], signed_at: d['signedAt']
129
+ )
130
+ end
131
+ end
132
+
133
+ class Failed
134
+ attr_reader :id, :ref_id, :error
135
+
136
+ def initialize(id:, error:, ref_id: nil)
137
+ @id = id; @ref_id = ref_id; @error = error
138
+ end
139
+
140
+ def deconstruct_keys(_) = { id: @id, ref_id: @ref_id, error: @error }
141
+
142
+ def self.from_json(d) = new(id: d['id'], ref_id: d['refId'], error: d['error'])
143
+ end
144
+
145
+ class Unknown
146
+ attr_reader :status
147
+
148
+ def initialize(status:)
149
+ @status = status
150
+ end
151
+
152
+ def deconstruct_keys(_) = { status: @status }
153
+ end
154
+
155
+ module Status
156
+ def self.from_json(d)
157
+ case d['status']
158
+ when 'PENDING' then Pending.from_json(d)
159
+ when 'COMPLETED' then Completed.from_json(d)
160
+ when 'FAILED' then Failed.from_json(d)
161
+ when String then Unknown.new(status: d['status'])
162
+ else raise IdentisureError.new(0, "Invalid freja status: #{d['status'].inspect}")
163
+ end
164
+ end
165
+ end
166
+
167
+ class Endpoint
168
+ # @api private
169
+ def initialize(transport)
170
+ @transport = transport
171
+ end
172
+
173
+ def auth(req) = Status.from_json(@transport.post('/v3/freja/auth', req.to_h))
174
+ def backchannel_auth(req) = Status.from_json(@transport.post('/v3/freja/backchannel/auth', req.to_h))
175
+ def sign(req) = Status.from_json(@transport.post('/v3/freja/sign', req.to_h))
176
+ def backchannel_sign(req) = Status.from_json(@transport.post('/v3/freja/backchannel/sign', req.to_h))
177
+ def auth_status(id) = Status.from_json(@transport.get("/v3/freja/auth/#{id}"))
178
+ def sign_status(id) = Status.from_json(@transport.get("/v3/freja/sign/#{id}"))
179
+ def cancel_auth(id) = @transport.delete("/v3/freja/auth/#{id}")
180
+ def cancel_sign(id) = @transport.delete("/v3/freja/sign/#{id}")
181
+
182
+ def age_verification(req)
183
+ Identisure::AgeVerification::Status.from_json(@transport.post('/v3/freja/age-verification', req.to_h))
184
+ end
185
+
186
+ def age_verification_status(id)
187
+ Identisure::AgeVerification::Status.from_json(@transport.get("/v3/freja/age-verification/#{id}"))
188
+ end
189
+
190
+ def cancel_age_verification(id) = @transport.delete("/v3/freja/age-verification/#{id}")
191
+
192
+ def wait_for_auth(id, opts: PollOptions.new) = poll(opts) { auth_status(id) }
193
+ def wait_for_sign(id, opts: PollOptions.new) = poll(opts) { sign_status(id) }
194
+
195
+ def wait_for_age_verification(id, opts: PollOptions.new)
196
+ poll_age(opts) { age_verification_status(id) }
197
+ end
198
+
199
+ private
200
+
201
+ def poll(opts)
202
+ deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + opts.timeout
203
+ loop do
204
+ status = yield
205
+ return status unless status.is_a?(Pending)
206
+ raise WaitError.new(timeout: true) if Process.clock_gettime(Process::CLOCK_MONOTONIC) >= deadline
207
+
208
+ sleep opts.interval
209
+ end
210
+ end
211
+
212
+ def poll_age(opts)
213
+ deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + opts.timeout
214
+ loop do
215
+ status = yield
216
+ return status unless status.is_a?(Identisure::AgeVerification::Pending)
217
+ raise WaitError.new(timeout: true) if Process.clock_gettime(Process::CLOCK_MONOTONIC) >= deadline
218
+
219
+ sleep opts.interval
220
+ end
221
+ end
222
+ end
223
+ end
224
+ end
@@ -0,0 +1,168 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Identisure
4
+ module Ftn
5
+ # --- Request ---
6
+
7
+ class AuthRequest
8
+ attr_reader :redirect_url, :request_phone, :request_email, :request_address, :ref_id
9
+
10
+ def initialize(redirect_url: nil, request_phone: nil, request_email: nil,
11
+ request_address: nil, ref_id: nil)
12
+ @redirect_url = redirect_url
13
+ @request_phone = request_phone
14
+ @request_email = request_email
15
+ @request_address = request_address
16
+ @ref_id = ref_id
17
+ end
18
+
19
+ def to_h
20
+ {
21
+ redirectUrl: @redirect_url,
22
+ requestPhone: @request_phone,
23
+ requestEmail: @request_email,
24
+ requestAddress: @request_address,
25
+ refId: @ref_id,
26
+ }.compact
27
+ end
28
+ end
29
+
30
+ # --- Status variants ---
31
+
32
+ class Pending
33
+ attr_reader :id, :ref_id, :url
34
+
35
+ def initialize(id:, url:, ref_id: nil)
36
+ @id = id; @ref_id = ref_id; @url = url
37
+ end
38
+
39
+ def deconstruct_keys(_) = { id: @id, ref_id: @ref_id, url: @url }
40
+
41
+ def self.from_json(d) = new(id: d['id'], ref_id: d['refId'], url: d['url'])
42
+ end
43
+
44
+ class Completed
45
+ attr_reader :id, :ref_id, :ssn, :name, :given_name, :surname,
46
+ :phone, :email, :address, :birth_date, :pid, :bank_id,
47
+ :bank_name, :authenticated_at
48
+
49
+ def initialize(id:, ssn:, name:, given_name:, surname:,
50
+ ref_id: nil, phone: nil, email: nil, address: nil,
51
+ birth_date: nil, pid: nil, bank_id: nil, bank_name: nil,
52
+ authenticated_at: nil)
53
+ @id = id; @ref_id = ref_id; @ssn = ssn; @name = name
54
+ @given_name = given_name; @surname = surname
55
+ @phone = phone; @email = email; @address = address
56
+ @birth_date = birth_date; @pid = pid; @bank_id = bank_id
57
+ @bank_name = bank_name; @authenticated_at = authenticated_at
58
+ end
59
+
60
+ def deconstruct_keys(_)
61
+ { id: @id, ref_id: @ref_id, ssn: @ssn, name: @name, given_name: @given_name,
62
+ surname: @surname, phone: @phone, email: @email, address: @address,
63
+ birth_date: @birth_date, pid: @pid, bank_id: @bank_id,
64
+ bank_name: @bank_name, authenticated_at: @authenticated_at }
65
+ end
66
+
67
+ def self.from_json(d)
68
+ new(id: d['id'], ref_id: d['refId'], ssn: d['ssn'], name: d['name'],
69
+ given_name: d['givenName'], surname: d['surname'],
70
+ phone: d['phone'], email: d['email'], address: d['address'],
71
+ birth_date: d['birthDate'], pid: d['pid'], bank_id: d['bankId'],
72
+ bank_name: d['bankName'], authenticated_at: d['authenticatedAt'])
73
+ end
74
+ end
75
+
76
+ class Failed
77
+ attr_reader :id, :ref_id, :error
78
+
79
+ def initialize(id:, error:, ref_id: nil)
80
+ @id = id; @ref_id = ref_id; @error = error
81
+ end
82
+
83
+ def deconstruct_keys(_) = { id: @id, ref_id: @ref_id, error: @error }
84
+
85
+ def self.from_json(d) = new(id: d['id'], ref_id: d['refId'], error: d['error'])
86
+ end
87
+
88
+ class Unknown
89
+ attr_reader :status
90
+
91
+ def initialize(status:)
92
+ @status = status
93
+ end
94
+
95
+ def deconstruct_keys(_) = { status: @status }
96
+ end
97
+
98
+ module Status
99
+ def self.from_json(d)
100
+ case d['status']
101
+ when 'PENDING' then Pending.from_json(d)
102
+ when 'COMPLETED' then Completed.from_json(d)
103
+ when 'FAILED' then Failed.from_json(d)
104
+ when String then Unknown.new(status: d['status'])
105
+ else raise IdentisureError.new(0, "Invalid ftn status: #{d['status'].inspect}")
106
+ end
107
+ end
108
+ end
109
+
110
+ # --- Endpoint ---
111
+
112
+ class Endpoint
113
+ # @api private
114
+ def initialize(transport)
115
+ @transport = transport
116
+ end
117
+
118
+ def auth(req)
119
+ Status.from_json(@transport.post('/v3/ftn/auth', req.to_h))
120
+ end
121
+
122
+ def age_verification(req)
123
+ Identisure::AgeVerification::Status.from_json(@transport.post('/v3/ftn/age-verification', req.to_h))
124
+ end
125
+
126
+ def auth_status(id) = Status.from_json(@transport.get("/v3/ftn/auth/#{id}"))
127
+
128
+ def age_verification_status(id)
129
+ Identisure::AgeVerification::Status.from_json(@transport.get("/v3/ftn/age-verification/#{id}"))
130
+ end
131
+
132
+ def cancel_auth(id) = @transport.delete("/v3/ftn/auth/#{id}")
133
+ def cancel_age_verification(id) = @transport.delete("/v3/ftn/age-verification/#{id}")
134
+
135
+ def wait_for_auth(id, opts: PollOptions.new)
136
+ poll_ftn(opts) { auth_status(id) }
137
+ end
138
+
139
+ def wait_for_age_verification(id, opts: PollOptions.new)
140
+ poll_age(opts) { age_verification_status(id) }
141
+ end
142
+
143
+ private
144
+
145
+ def poll_ftn(opts)
146
+ deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + opts.timeout
147
+ loop do
148
+ status = yield
149
+ return status unless status.is_a?(Pending)
150
+ raise WaitError.new(timeout: true) if Process.clock_gettime(Process::CLOCK_MONOTONIC) >= deadline
151
+
152
+ sleep opts.interval
153
+ end
154
+ end
155
+
156
+ def poll_age(opts)
157
+ deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + opts.timeout
158
+ loop do
159
+ status = yield
160
+ return status unless status.is_a?(Identisure::AgeVerification::Pending)
161
+ raise WaitError.new(timeout: true) if Process.clock_gettime(Process::CLOCK_MONOTONIC) >= deadline
162
+
163
+ sleep opts.interval
164
+ end
165
+ end
166
+ end
167
+ end
168
+ end
@@ -0,0 +1,205 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Identisure
4
+ module MitId
5
+ class AuthRequest
6
+ attr_reader :redirect_url, :reference_text, :request_phone, :request_email,
7
+ :request_address, :ref_id
8
+
9
+ def initialize(redirect_url: nil, reference_text: nil, request_phone: nil,
10
+ request_email: nil, request_address: nil, ref_id: nil)
11
+ @redirect_url = redirect_url; @reference_text = reference_text
12
+ @request_phone = request_phone; @request_email = request_email
13
+ @request_address = request_address; @ref_id = ref_id
14
+ end
15
+
16
+ def to_h
17
+ {
18
+ redirectUrl: @redirect_url, referenceText: @reference_text,
19
+ requestPhone: @request_phone, requestEmail: @request_email,
20
+ requestAddress: @request_address, refId: @ref_id
21
+ }.compact
22
+ end
23
+ end
24
+
25
+ class BackchannelAuthRequest
26
+ attr_reader :ssn, :binding_message, :callback_url, :ref_id
27
+
28
+ def initialize(ssn:, binding_message:, callback_url: nil, ref_id: nil)
29
+ @ssn = ssn; @binding_message = binding_message
30
+ @callback_url = callback_url; @ref_id = ref_id
31
+ end
32
+
33
+ def to_h
34
+ {
35
+ ssn: @ssn, bindingMessage: @binding_message,
36
+ callbackUrl: @callback_url, refId: @ref_id
37
+ }.compact
38
+ end
39
+ end
40
+
41
+ class SignRequest
42
+ attr_reader :text, :redirect_url, :ref_id
43
+
44
+ def initialize(text:, redirect_url: nil, ref_id: nil)
45
+ @text = text; @redirect_url = redirect_url; @ref_id = ref_id
46
+ end
47
+
48
+ def to_h
49
+ { text: @text, redirectUrl: @redirect_url, refId: @ref_id }.compact
50
+ end
51
+ end
52
+
53
+ class SignResult
54
+ attr_reader :checksum
55
+
56
+ def initialize(checksum:)
57
+ @checksum = checksum
58
+ end
59
+
60
+ def deconstruct_keys(_) = { checksum: @checksum }
61
+ def self.from_json(d) = new(checksum: d['checksum'])
62
+ end
63
+
64
+ class Pending
65
+ attr_reader :id, :ref_id, :url, :binding_message
66
+
67
+ def initialize(id:, ref_id: nil, url: nil, binding_message: nil)
68
+ @id = id; @ref_id = ref_id; @url = url; @binding_message = binding_message
69
+ end
70
+
71
+ def deconstruct_keys(_)
72
+ { id: @id, ref_id: @ref_id, url: @url, binding_message: @binding_message }
73
+ end
74
+
75
+ def self.from_json(d)
76
+ new(id: d['id'], ref_id: d['refId'], url: d['url'], binding_message: d['bindingMessage'])
77
+ end
78
+ end
79
+
80
+ class Completed
81
+ attr_reader :id, :ref_id, :ssn, :name, :given_name, :surname,
82
+ :phone, :email, :address, :birth_date, :pid, :bank_id,
83
+ :sign_result, :authenticated_at, :signed_at
84
+
85
+ def initialize(id:, ssn:, name:, given_name:, surname:, ref_id: nil,
86
+ phone: nil, email: nil, address: nil, birth_date: nil,
87
+ pid: nil, bank_id: nil, sign_result: nil,
88
+ authenticated_at: nil, signed_at: nil)
89
+ @id = id; @ref_id = ref_id; @ssn = ssn; @name = name
90
+ @given_name = given_name; @surname = surname; @phone = phone
91
+ @email = email; @address = address; @birth_date = birth_date
92
+ @pid = pid; @bank_id = bank_id; @sign_result = sign_result
93
+ @authenticated_at = authenticated_at; @signed_at = signed_at
94
+ end
95
+
96
+ def deconstruct_keys(_)
97
+ { id: @id, ref_id: @ref_id, ssn: @ssn, name: @name, given_name: @given_name,
98
+ surname: @surname, phone: @phone, email: @email, address: @address,
99
+ birth_date: @birth_date, pid: @pid, bank_id: @bank_id,
100
+ sign_result: @sign_result, authenticated_at: @authenticated_at,
101
+ signed_at: @signed_at }
102
+ end
103
+
104
+ def self.from_json(d)
105
+ new(
106
+ id: d['id'], ref_id: d['refId'], ssn: d['ssn'], name: d['name'],
107
+ given_name: d['givenName'], surname: d['surname'], phone: d['phone'],
108
+ email: d['email'], address: d['address'], birth_date: d['birthDate'],
109
+ pid: d['pid'], bank_id: d['bankId'],
110
+ sign_result: d['signResult'] ? SignResult.from_json(d['signResult']) : nil,
111
+ authenticated_at: d['authenticatedAt'], signed_at: d['signedAt']
112
+ )
113
+ end
114
+ end
115
+
116
+ class Failed
117
+ attr_reader :id, :ref_id, :error
118
+
119
+ def initialize(id:, error:, ref_id: nil)
120
+ @id = id; @ref_id = ref_id; @error = error
121
+ end
122
+
123
+ def deconstruct_keys(_) = { id: @id, ref_id: @ref_id, error: @error }
124
+ def self.from_json(d) = new(id: d['id'], ref_id: d['refId'], error: d['error'])
125
+ end
126
+
127
+ class Unknown
128
+ attr_reader :status
129
+
130
+ def initialize(status:)
131
+ @status = status
132
+ end
133
+
134
+ def deconstruct_keys(_) = { status: @status }
135
+ end
136
+
137
+ module Status
138
+ def self.from_json(d)
139
+ case d['status']
140
+ when 'PENDING' then Pending.from_json(d)
141
+ when 'COMPLETED' then Completed.from_json(d)
142
+ when 'FAILED' then Failed.from_json(d)
143
+ when String then Unknown.new(status: d['status'])
144
+ else raise IdentisureError.new(0, "Invalid mitid status: #{d['status'].inspect}")
145
+ end
146
+ end
147
+ end
148
+
149
+ class Endpoint
150
+ # @api private
151
+ def initialize(transport)
152
+ @transport = transport
153
+ end
154
+
155
+ def auth(req) = Status.from_json(@transport.post('/v3/mitid/auth', req.to_h))
156
+ def backchannel_auth(req) = Status.from_json(@transport.post('/v3/mitid/backchannel/auth', req.to_h))
157
+ def sign(req) = Status.from_json(@transport.post('/v3/mitid/sign', req.to_h))
158
+ def auth_status(id) = Status.from_json(@transport.get("/v3/mitid/auth/#{id}"))
159
+ def sign_status(id) = Status.from_json(@transport.get("/v3/mitid/sign/#{id}"))
160
+ def cancel_auth(id) = @transport.delete("/v3/mitid/auth/#{id}")
161
+ def cancel_sign(id) = @transport.delete("/v3/mitid/sign/#{id}")
162
+
163
+ def age_verification(req)
164
+ Identisure::AgeVerification::Status.from_json(@transport.post('/v3/mitid/age-verification', req.to_h))
165
+ end
166
+
167
+ def age_verification_status(id)
168
+ Identisure::AgeVerification::Status.from_json(@transport.get("/v3/mitid/age-verification/#{id}"))
169
+ end
170
+
171
+ def cancel_age_verification(id) = @transport.delete("/v3/mitid/age-verification/#{id}")
172
+
173
+ def wait_for_auth(id, opts: PollOptions.new) = poll(opts) { auth_status(id) }
174
+ def wait_for_sign(id, opts: PollOptions.new) = poll(opts) { sign_status(id) }
175
+
176
+ def wait_for_age_verification(id, opts: PollOptions.new)
177
+ poll_age(opts) { age_verification_status(id) }
178
+ end
179
+
180
+ private
181
+
182
+ def poll(opts)
183
+ deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + opts.timeout
184
+ loop do
185
+ status = yield
186
+ return status unless status.is_a?(Pending)
187
+ raise WaitError.new(timeout: true) if Process.clock_gettime(Process::CLOCK_MONOTONIC) >= deadline
188
+
189
+ sleep opts.interval
190
+ end
191
+ end
192
+
193
+ def poll_age(opts)
194
+ deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + opts.timeout
195
+ loop do
196
+ status = yield
197
+ return status unless status.is_a?(Identisure::AgeVerification::Pending)
198
+ raise WaitError.new(timeout: true) if Process.clock_gettime(Process::CLOCK_MONOTONIC) >= deadline
199
+
200
+ sleep opts.interval
201
+ end
202
+ end
203
+ end
204
+ end
205
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Identisure
4
+ class PollOptions
5
+ attr_reader :interval, :timeout
6
+
7
+ def initialize(interval: 2, timeout: 300)
8
+ @interval = interval
9
+ @timeout = timeout
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'faraday'
4
+ require 'json'
5
+ require 'securerandom'
6
+
7
+ module Identisure
8
+ # @api private
9
+ USER_AGENT = "identisure-client-ruby/#{Identisure::VERSION}".freeze
10
+
11
+ # @api private
12
+ class Transport
13
+ def initialize(connection, base_url, client_id, client_secret, timeout)
14
+ @conn = connection
15
+ @timeout = timeout
16
+ @base_url = base_url.chomp('/')
17
+ @authorization = "Basic #{["#{client_id}:#{client_secret}"].pack('m0')}"
18
+ end
19
+
20
+ def inspect = "#<#{self.class.name}>"
21
+
22
+ def post(path, body)
23
+ parse(send_request(:post, path, JSON.generate(body), 'Content-Type' => 'application/json'))
24
+ end
25
+
26
+ def get(path)
27
+ parse(send_request(:get, path))
28
+ end
29
+
30
+ def get_raw(path)
31
+ send_request(:get, path).body.to_s
32
+ end
33
+
34
+ def delete(path)
35
+ send_request(:delete, path)
36
+ nil
37
+ end
38
+
39
+ def post_multipart(path, data, filename, mime_type)
40
+ boundary = "identisure-#{SecureRandom.hex(16)}"
41
+ body = multipart_body(boundary, data, filename, mime_type)
42
+ parse(send_request(:post, path, body, 'Content-Type' => "multipart/form-data; boundary=#{boundary}"))
43
+ end
44
+
45
+ private
46
+
47
+ def multipart_body(boundary, data, filename, mime_type)
48
+ escaped = filename.gsub('"', '%22').gsub("\r", '%0D').gsub("\n", '%0A')
49
+ body = String.new(encoding: Encoding::BINARY)
50
+ body << "--#{boundary}\r\n".b
51
+ body << %(Content-Disposition: form-data; name="file"; filename="#{escaped}"\r\n).b
52
+ body << "Content-Type: #{mime_type}\r\n\r\n".b
53
+ body << data.b
54
+ body << "\r\n--#{boundary}--\r\n".b
55
+ end
56
+
57
+ def send_request(method, path, body = nil, headers = {})
58
+ headers = headers.merge('Authorization' => @authorization, 'User-Agent' => USER_AGENT)
59
+ res = @conn.run_request(method, @base_url + path, body, headers) do |req|
60
+ req.options.timeout = @timeout
61
+ end
62
+ raise IdentisureError.new(res.status, error_message(res.body)) unless res.success?
63
+
64
+ res
65
+ rescue Faraday::Error => e
66
+ raise IdentisureError.new(0, e.message)
67
+ end
68
+
69
+ def parse(res)
70
+ body = res.body
71
+ return body if body.is_a?(Hash)
72
+
73
+ parsed = JSON.parse(body.to_s)
74
+ raise IdentisureError.new(res.status, 'Response body is not a JSON object') unless parsed.is_a?(Hash)
75
+
76
+ parsed
77
+ rescue JSON::ParserError => e
78
+ raise IdentisureError.new(res.status, "Invalid JSON response: #{e.message}")
79
+ end
80
+
81
+ def error_message(body)
82
+ return body['message'] || body.to_s if body.is_a?(Hash)
83
+
84
+ parsed = JSON.parse(body.to_s)
85
+ parsed.is_a?(Hash) && parsed['message'] ? parsed['message'] : body.to_s
86
+ rescue JSON::ParserError
87
+ body.to_s
88
+ end
89
+ end
90
+ end