client_for_poslynx 0.9.0 → 1.0.0.pre

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,50 @@
1
+ # coding: utf-8
2
+
3
+ module ClientForPoslynx
4
+ module Net
5
+ class EM_Connector
6
+
7
+ def self.CallbackMap(*args)
8
+ if args.length == 1 && EMC::CallbackMap === args.first
9
+ return args.first
10
+ else
11
+ EMC::CallbackMap.new( *args )
12
+ end
13
+ end
14
+
15
+ class CallbackMap
16
+ def initialize(callable_map={})
17
+ @callable_map = callable_map
18
+ end
19
+
20
+ def ==(other)
21
+ callable_map == other.callable_map
22
+ end
23
+
24
+ def [](callback_key)
25
+ callable_map[callback_key]
26
+ end
27
+
28
+ def to_hash
29
+ callable_map.dup
30
+ end
31
+
32
+ def merge(other)
33
+ self.class.new(
34
+ callable_map.merge( other.to_hash )
35
+ )
36
+ end
37
+
38
+ def call(callback_key, *args)
39
+ callback = callable_map[callback_key]
40
+ callback.call *args if callback
41
+ end
42
+
43
+ protected
44
+
45
+ attr_reader :callable_map
46
+ end
47
+
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,14 @@
1
+ # coding: utf-8
2
+
3
+ module ClientForPoslynx
4
+ module Net
5
+ class EM_Connector
6
+
7
+ class ConnectionHandler < EM::Connection
8
+ include EM::Protocols::POSLynx
9
+ include EMC::HandlesConnection
10
+ end
11
+
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,24 @@
1
+ # coding: utf-8
2
+
3
+ module ClientForPoslynx
4
+ module Net
5
+ class EM_Connector
6
+
7
+ class State < Struct.new( :encryption, :connection, :connection_status, :status_of_request )
8
+
9
+ def connection_initial? ; connection_status == :initial ; end
10
+ def connecting? ; connection_status == :connecting ; end
11
+ def connected? ; connection_status == :connected ; end
12
+ def disconnecting? ; connection_status == :disconnecting ; end
13
+ def disconnecting? ; connection_status == :disconnected ; end
14
+
15
+ def request_initial? ; status_of_request == :initial ; end
16
+ def request_pending? ; status_of_request == :pending ; end
17
+ def got_response? ; status_of_request == :got_response ; end
18
+ def request_failed? ; status_of_request == :failed ; end
19
+
20
+ end
21
+
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,66 @@
1
+ # coding: utf-8
2
+
3
+ module ClientForPoslynx
4
+ module Net
5
+ class EM_Connector
6
+
7
+ class EventDispatcher
8
+ NULL_LISTENER = ->(*) { }
9
+
10
+ class << self
11
+ private :new
12
+
13
+ def null(connection)
14
+ new( connection )
15
+ end
16
+
17
+ def for_connect(connection, opts)
18
+ original_dispatcher = opts[:original_dispatcher]
19
+ callback_map = EMC.CallbackMap(
20
+ connection_completed: opts[:on_success],
21
+ unbind: opts[:on_failure],
22
+ )
23
+ new( connection, original_dispatcher, callback_map )
24
+ end
25
+
26
+ def for_disconnect(connection, opts)
27
+ callback_map = EMC.CallbackMap(
28
+ unbind: opts[:on_completed]
29
+ )
30
+ new( connection, nil, callback_map )
31
+ end
32
+
33
+ def for_send_request(connection, opts)
34
+ callback_map = EMC.CallbackMap(
35
+ receive_response: opts[:on_response],
36
+ unbind: opts[:on_failure],
37
+ )
38
+ new( connection, nil, callback_map )
39
+ end
40
+ end
41
+
42
+ def initialize(connection, original_dispatcher = nil, callback_map = EMC.CallbackMap())
43
+ @connection = connection
44
+ @original_dispatcher = original_dispatcher
45
+ @callback_map = callback_map
46
+ end
47
+
48
+ def []=(event_type, callback)
49
+ callback_map[event_type] = callback
50
+ end
51
+
52
+ def event_occurred(event_type, *args)
53
+ connection.reset_event_dispatcher
54
+ original_dispatcher.event_occurred( event_type, *args ) if original_dispatcher
55
+ callback_map.call event_type, *args
56
+ end
57
+
58
+ private
59
+
60
+ attr_reader :connection, :original_dispatcher, :callback_map
61
+
62
+ end
63
+
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,57 @@
1
+ # coding: utf-8
2
+
3
+ module ClientForPoslynx
4
+ module Net
5
+ class EM_Connector
6
+
7
+ module HandlesConnection
8
+ def initialize(connector_state)
9
+ @connector_state = connector_state
10
+ connector_state.connection = self
11
+ connector_state.connection_status = :connecting
12
+ end
13
+
14
+ attr_writer :event_dispatcher
15
+
16
+ def event_dispatcher
17
+ @event_dispatcher ||= EMC::EventDispatcher.null( self )
18
+ end
19
+
20
+ def reset_event_dispatcher
21
+ self.event_dispatcher = nil
22
+ end
23
+
24
+ def connection_completed
25
+ if connector_state.encryption == :use_ssl
26
+ start_tls
27
+ else
28
+ connector_state.connection_status = :connected
29
+ event_dispatcher.event_occurred :connection_completed
30
+ end
31
+ end
32
+
33
+ def ssl_handshake_completed
34
+ connector_state.connection_status = :connected
35
+ event_dispatcher.event_occurred :connection_completed
36
+ end
37
+
38
+ def unbind
39
+ connector_state.connection_status = :disconnected
40
+ connector_state.status_of_request = :failed if connector_state.request_pending?
41
+ event_dispatcher.event_occurred :unbind
42
+ end
43
+
44
+ def receive_response(response_data)
45
+ connector_state.status_of_request = :got_response if connector_state.request_pending?
46
+ event_dispatcher.event_occurred :receive_response, response_data
47
+ end
48
+
49
+ private
50
+
51
+ attr_reader :connector_state
52
+
53
+ end
54
+
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+
3
+ module ClientForPoslynx
4
+ module Net
5
+ class EM_Connector
6
+
7
+ def self.RequestCall(*args)
8
+ if args.length == 1 && self::RequestCall === args.first
9
+ args.first
10
+ elsif args.first.nil?
11
+ RequestCall.new(nil, {})
12
+ else
13
+ RequestCall.new( *args )
14
+ end
15
+ end
16
+
17
+ class RequestCall < Struct.new(:request_data, :result_callbacks)
18
+ def initialize(request_data, result_callbacks={})
19
+ result_callbacks = EMC.CallbackMap( result_callbacks )
20
+ super request_data, result_callbacks
21
+ freeze
22
+ end
23
+ end
24
+
25
+ end
26
+ end
27
+ end
@@ -2,7 +2,7 @@
2
2
 
3
3
  require 'eventmachine'
4
4
 
5
- module EventMachine
5
+ module EM
6
6
  module Protocols
7
7
 
8
8
  # Sends requests to and receives responses from a Precidia
@@ -0,0 +1,250 @@
1
+ # coding: utf-8
2
+
3
+ module ClientForPoslynx
4
+ module Net
5
+
6
+ # Provides a synchronous, structured API for making
7
+ # requests to POSLynx host using Event Machine and returning
8
+ # responses when received.
9
+ #
10
+ # Eliminates the need for complicated and messy event
11
+ # callback chains in POSLynx client code.
12
+ #
13
+ # Multiple sessions may be in effect at the same time,
14
+ # interacting with the same POSLyx host by using the same
15
+ # Event Machine connector. This is important for performing
16
+ # actions such as initiating a new payment interaction when
17
+ # a previous interaction was abandoned.
18
+ class EM_Session
19
+
20
+ # Base class for errors raised by the session to code that
21
+ # is running in the context of a sesssion.
22
+ class Error < StandardError ; end
23
+
24
+ # Raised when an error condition is detected while making
25
+ # a request or while waiting for the response.
26
+ class RequestError < Error ; end
27
+
28
+ # Raised when attempting to make a request while another
29
+ # session is waiting for a response to a request of the
30
+ # same type (other than PinPadReset).
31
+ class ConflictingRequestError < Error ; end
32
+
33
+ # Executes the given block in the context of a session
34
+ # attached to the given Event Machine connector. The
35
+ # session is passed as the argument to the block.
36
+ def self.execute(connector)
37
+ new( connector ).execute { |s| yield s }
38
+ end
39
+
40
+ attr_reader :connector
41
+
42
+ attr_reader :status
43
+
44
+ attr_reader :em_system
45
+
46
+ # Builds a new EM_Session instance attached to the given
47
+ # Event Machine connector.
48
+ #
49
+ # ==== Options
50
+ # * <tt>:em_system</tt> - The event machine system that
51
+ # will be called on for operations such as adding timers
52
+ # and deferring actions.
53
+ def initialize(connector, opts={})
54
+ self.connector = connector
55
+ @em_system = opts.fetch( :em_system, ::EM )
56
+ self.status = :initialized
57
+ end
58
+
59
+ # Executes the given block in the context of the session
60
+ # The session is passed as the argument to the block.
61
+ def execute
62
+ @fiber = Fiber.new do
63
+ yield self
64
+ :done
65
+ end
66
+ self.status = :engaged
67
+ dispatch fiber.resume
68
+ end
69
+
70
+ # Called from within an executed block of code for the
71
+ # session to send request data to the POSLynx and return
72
+ # the response to the request once it is received.
73
+ # If a connection could not be established or is lost
74
+ # before a response can be received, then a
75
+ # <tt>RequestError</tt> exception will be raised.
76
+ #
77
+ # If another session attempts to supplant this request, but
78
+ # the response to this request is subsequently received,
79
+ # then the response is returned as normal, but this
80
+ # session's status is changed to detached, and any
81
+ # subsequent request attempts made in this session will
82
+ # result in <tt>RequestError</tt> being raised.
83
+ #
84
+ # If another session is already waiting for a response,
85
+ # then this will attempt to usurp or supplant the other
86
+ # request. If the new request is of the same type as the
87
+ # existing request, and the type is not PinPadReset, then
88
+ # this call will raise a <tt>ConflictingRequestError</tt>
89
+ # exception.
90
+ def request(data)
91
+ if status == :detached
92
+ raise RequestError, "Session cannot make requests because it is detached"
93
+ end
94
+ if connector.request_pending?
95
+ pending_request_data = connector.latest_request.request_data
96
+ pending_callbacks = connector.latest_request.result_callbacks
97
+ if Data::Requests::PinPadReset === data && Data::Requests::PinPadReset === pending_request_data
98
+ pending_callbacks.call :on_failure
99
+ was_successful, resp_data_or_ex = Fiber.yield( [:_get_response] )
100
+ elsif data.class == pending_request_data.class
101
+ raise ConflictingRequestError, "Attempted a request while another request of the same type is in progress"
102
+ else
103
+ was_successful, resp_data_or_ex = Fiber.yield( [:_request, data, connector.latest_request] )
104
+ end
105
+ else
106
+ was_successful, resp_data_or_ex = Fiber.yield( [:_request, data] )
107
+ end
108
+ raise resp_data_or_ex unless was_successful
109
+ resp_data_or_ex
110
+ end
111
+
112
+ # Given a block argument, returns control to EventMachine,
113
+ # executes the block in a separate thread, waits for the
114
+ # thread to complete, and then returns the value returned
115
+ # by the block.
116
+ #
117
+ # This is implemented behind the scenes using
118
+ # EventMachine::defer and has the same considerations and
119
+ # caveats.
120
+ #
121
+ # Note that methods of the session should not be called by
122
+ # code within the block since those methods should only be
123
+ # called from code running in the main event loop thread.
124
+ def exec_dissociated(&block)
125
+ was_successful, resp_data_or_ex = Fiber.yield( [:_exec_dissociated, block] )
126
+ raise resp_data_or_ex unless was_successful
127
+ resp_data_or_ex
128
+ end
129
+
130
+ # Returns control to EventMachine, and returns control to
131
+ # the session code after the given delay-time in seconds.
132
+ def sleep(delay_time)
133
+ Fiber.yield [:_sleep, delay_time]
134
+ end
135
+
136
+ private
137
+
138
+ attr_writer :connector, :status
139
+ attr_reader :fiber
140
+
141
+ def dispatch(fiber_callback)
142
+ return if fiber_callback == :done
143
+ send *fiber_callback
144
+ end
145
+
146
+ def _request(data, overlaps_request=nil)
147
+ connector.connect(
148
+ on_success: ->() {
149
+ send_request_callbacks = response_handlers( overlaps_request )
150
+ connector.send_request data, send_request_callbacks
151
+ },
152
+ on_failure: ->() {
153
+ dispatch fiber.resume( [false, RequestError.new] )
154
+ }
155
+ )
156
+ rescue => ex
157
+ dispatch fiber.resume( [false, ex] )
158
+ end
159
+
160
+ def _get_response
161
+ connector.get_response response_handlers
162
+ end
163
+
164
+ def response_handlers(overlaps_request=nil)
165
+ overlapped_request_data = overlaps_request && overlaps_request.request_data
166
+ overlapped_request_callbacks = overlaps_request && overlaps_request.result_callbacks
167
+ {
168
+ on_response: on_response_handler( overlaps_request ),
169
+ on_failure: on_failure_handler( overlaps_request ),
170
+ on_detached: ->(){ detach! },
171
+ }
172
+ end
173
+
174
+ def on_response_handler(overlaps_request)
175
+ overlaps_request ?
176
+ on_response_handler_with_overlap( overlaps_request ) :
177
+ simple_on_response_handler
178
+ end
179
+
180
+ def on_response_handler_with_overlap(overlaps_request)
181
+ overlapped_request_data = overlaps_request && overlaps_request.request_data
182
+ overlapped_request_callbacks = overlaps_request && overlaps_request.result_callbacks
183
+ ->(response_data) {
184
+ if overlapped_request_data.potential_response?( response_data )
185
+ overlapped_request_callbacks.call :on_detached
186
+ overlapped_request_callbacks.call :on_response, response_data
187
+ _get_response
188
+ else
189
+ overlapped_request_callbacks.call :on_failure
190
+ dispatch fiber.resume( [true, response_data] )
191
+ end
192
+ }
193
+ end
194
+
195
+ def simple_on_response_handler
196
+ ->(response_data) {
197
+ dispatch fiber.resume( [true, response_data] )
198
+ }
199
+ end
200
+
201
+ def on_failure_handler(overlaps_request)
202
+ overlaps_request ?
203
+ on_failure_handler_with_overlap( overlaps_request ) :
204
+ simple_on_failure_handler
205
+ end
206
+
207
+ def on_failure_handler_with_overlap(overlaps_request)
208
+ overlapped_request_callbacks = overlaps_request && overlaps_request.result_callbacks
209
+ ->() {
210
+ overlapped_request_callbacks.call :on_failure if overlaps_request
211
+ dispatch fiber.resume( [false, RequestError.new] )
212
+ }
213
+ end
214
+
215
+ def simple_on_failure_handler
216
+ ->() {
217
+ dispatch fiber.resume( [false, RequestError.new] )
218
+ }
219
+ end
220
+
221
+ def detach!
222
+ self.connector = nil
223
+ self.status = :detached
224
+ end
225
+
226
+ def _exec_dissociated op
227
+ wrapped_op = ->() {
228
+ begin
229
+ result = op.call
230
+ [true, result]
231
+ rescue => ex
232
+ [false, ex]
233
+ end
234
+ }
235
+ callback = ->(result) do
236
+ dispatch fiber.resume result
237
+ end
238
+ em_system.defer wrapped_op, callback
239
+ end
240
+
241
+ def _sleep(delay_time)
242
+ callback = ->() {
243
+ dispatch fiber.resume
244
+ }
245
+ em_system.add_timer delay_time, callback
246
+ end
247
+ end
248
+
249
+ end
250
+ end