ringcentral_sdk 1.3.4 → 2.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.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +12 -0
  3. data/Gemfile.lock +70 -33
  4. data/{LICENSE.txt → LICENSE.md} +0 -0
  5. data/README.md +40 -76
  6. data/lib/ringcentral_sdk.rb +4 -3
  7. data/lib/ringcentral_sdk/rest.rb +18 -17
  8. data/lib/ringcentral_sdk/rest/cache.rb +9 -3
  9. data/lib/ringcentral_sdk/rest/cache/extensions.rb +91 -94
  10. data/lib/ringcentral_sdk/rest/client.rb +196 -202
  11. data/lib/ringcentral_sdk/rest/configuration.rb +91 -0
  12. data/lib/ringcentral_sdk/rest/event.rb +44 -43
  13. data/lib/ringcentral_sdk/rest/extension.rb +12 -9
  14. data/lib/ringcentral_sdk/rest/extension_presence.rb +73 -78
  15. data/lib/ringcentral_sdk/rest/messages.rb +40 -31
  16. data/lib/ringcentral_sdk/rest/messages_retriever.rb +30 -33
  17. data/lib/ringcentral_sdk/rest/request.rb +10 -5
  18. data/lib/ringcentral_sdk/rest/request/base.rb +24 -19
  19. data/lib/ringcentral_sdk/rest/request/fax.rb +88 -91
  20. data/lib/ringcentral_sdk/rest/request/inflator.rb +9 -2
  21. data/lib/ringcentral_sdk/rest/request/inflator/contact_info.rb +19 -12
  22. data/lib/ringcentral_sdk/rest/request/simple.rb +24 -34
  23. data/lib/ringcentral_sdk/rest/simple_client.rb +63 -78
  24. data/lib/ringcentral_sdk/rest/subscription.rb +223 -228
  25. data/test/test_base.rb +5 -5
  26. data/test/test_client.rb +87 -88
  27. data/test/test_event.rb +28 -11
  28. data/test/test_extension_presence.rb +64 -62
  29. data/test/test_helper_fax.rb +46 -47
  30. data/test/test_helper_inflator_contact_info.rb +8 -10
  31. data/test/test_helper_request.rb +1 -1
  32. data/test/test_setup.rb +24 -21
  33. data/test/test_subscription.rb +46 -48
  34. metadata +72 -33
  35. data/lib/ringcentral_sdk/rest/config.rb +0 -102
  36. data/test/test_config.rb +0 -29
@@ -1,102 +1,87 @@
1
- module RingCentralSdk::REST
1
+ module RingCentralSdk
2
+ module REST
3
+ # A simplified, but still generic, REST interface.
4
+ #
5
+ # NOTE: This is an experimental module.
6
+ #
7
+ # client = RingCentralSdk::REST::Client.new ...
8
+ # simple = RingCentralSdk::REST::SimpleClient client
9
+ #
10
+ # simple.post(
11
+ # path: 'sms',
12
+ # body: {
13
+ # from: {phoneNumber: '+16505551212'},
14
+ # to: [{phoneNumber: '+14155551212'}],
15
+ # text: 'Hi There!'
16
+ # }
17
+ # )
18
+ class SimpleClient
19
+ attr_accessor :client
2
20
 
3
- # A simplified, but still generic, REST interface.
4
- #
5
- # NOTE: This is an experimental module.
6
- #
7
- # client = RingCentralSdk::REST::Client.new ...
8
- # simple = RingCentralSdk::REST::SimpleClient client
9
- #
10
- # simple.post(
11
- # path: 'sms',
12
- # body: {
13
- # from: {phoneNumber: '+16505551212'},
14
- # to: [{phoneNumber: '+14155551212'}],
15
- # text: 'Hi There!'
16
- # }
17
- # )
18
- class SimpleClient
19
- attr_accessor :client
20
-
21
- def initialize(client)
22
- @client = client
23
- end
24
-
25
- def send(request)
26
- if request.is_a?(RingCentralSdk::Helpers::Request)
27
- return @client.request(request)
28
- elsif ! request.is_a?(Hash)
29
- raise "Request is not a RingCentralSdk::Helpers::Request or Hash"
21
+ def initialize(client)
22
+ @client = client
30
23
  end
31
24
 
32
- verb = request.key?(:verb) ? request[:verb].to_s.downcase : 'get'
25
+ def send(request)
26
+ return @client.request(request) if request.is_a? RingCentralSdk::Helpers::Request
27
+ raise(ArgumentError, 'Request is not a ...Helpers::Request or Hash') unless request.is_a? Hash
33
28
 
34
- if verb == 'get'
35
- return get(request)
36
- elsif verb == 'post'
37
- return post(request)
38
- elsif verb == 'put'
39
- return put(request)
40
- elsif verb == 'delete'
41
- return delete(request)
42
- else
43
- raise 'Method not supported'
44
- end
45
- end
29
+ verb = request.key?(:verb) ? request[:verb].to_s.downcase : 'get'
46
30
 
47
- def delete(opts={})
48
- return @client.http.delete do |req|
49
- req.url build_url(opts[:path])
31
+ return get(request) if verb == 'get'
32
+ return post(request) if verb == 'post'
33
+ return put(request) if verb == 'put'
34
+ return delete(request) if verb == 'delete'
35
+ raise ArgumentError, "Method not supported #{verb}"
50
36
  end
51
- end
52
37
 
53
- def get(opts={})
54
- return @client.http.get do |req|
55
- req.url build_url(opts[:path])
38
+ def delete(opts = {})
39
+ @client.http.delete do |req|
40
+ req.url build_url(opts[:path])
41
+ end
56
42
  end
57
- end
58
43
 
59
- def post(opts={})
60
- return @client.http.post do |req|
61
- req = inflate_request req, opts
44
+ def get(opts = {})
45
+ @client.http.get do |req|
46
+ req.url build_url(opts[:path])
47
+ end
62
48
  end
63
- end
64
49
 
65
- def put(opts={})
66
- return @client.http.put do |req|
67
- req = inflate_request req, opts
50
+ def post(opts = {})
51
+ @client.http.post do |req|
52
+ req = inflate_request req, opts
53
+ end
68
54
  end
69
- end
70
55
 
71
- def inflate_request(req, opts={})
72
- req.url build_url(opts[:path])
73
- if opts.key? :body
74
- req.body = opts[:body]
75
- if opts[:body].is_a?(Hash)
76
- req.headers['Content-Type'] = 'application/json'
56
+ def put(opts = {})
57
+ @client.http.put do |req|
58
+ req = inflate_request req, opts
77
59
  end
78
60
  end
79
- req
80
- end
81
61
 
82
- def build_url(path)
83
- url = ''
84
- if !path.is_a?(Array)
85
- path = [path]
62
+ def inflate_request(req, opts = {})
63
+ req.url build_url(opts[:path])
64
+ if opts.key? :body
65
+ req.body = opts[:body]
66
+ if opts[:body].is_a?(Hash)
67
+ req.headers['Content-Type'] = 'application/json'
68
+ end
69
+ end
70
+ req
86
71
  end
87
- if path.length > 0
88
- path0 = path[0].to_s
89
- if path0 !~ /\//
90
- if path0.index('account') != 0
91
- if path0.index('extension') != 0
92
- path.unshift('extension/~')
93
- end
72
+
73
+ def build_url(path)
74
+ path = [path] unless path.is_a? Array
75
+
76
+ unless path.empty?
77
+ path0 = path[0].to_s
78
+ if path0.index('/').nil? && path0.index('account') != 0
79
+ path.unshift('extension/~') if path0.index('extension') != 0
94
80
  path.unshift('account/~')
95
81
  end
96
82
  end
83
+ path.join('/')
97
84
  end
98
- url = path.join('/')
99
- return url
100
85
  end
101
86
  end
102
87
  end
@@ -5,284 +5,279 @@ require 'observer'
5
5
  require 'openssl'
6
6
  require 'pubnub'
7
7
 
8
- module RingCentralSdk::REST
9
- class Subscription
10
- include Observable
11
-
12
- RENEW_HANDICAP = 60
13
-
14
- attr_reader :event_filters
15
-
16
- def initialize(client)
17
- @_client = client
18
- @event_filters = []
19
- @_timeout = nil
20
- @_subscription = nil_subscription()
21
- @_pubnub = nil
22
- @_logger_prefix = " -- #{self.class.name}: "
23
- end
8
+ module RingCentralSdk
9
+ module REST
10
+ # Subscription class is an observerable class that represents
11
+ # one RingCentral subscription using the PubNub transport via
12
+ # the Subscription API
13
+ class Subscription
14
+ include Observable
15
+
16
+ RENEW_HANDICAP = 60
17
+
18
+ attr_reader :event_filters
19
+
20
+ def initialize(client)
21
+ @_client = client
22
+ @event_filters = []
23
+ @_timeout = nil
24
+ @_subscription = nil_subscription
25
+ @_pubnub = nil
26
+ @_logger_prefix = " -- #{self.class.name}: "
27
+ end
24
28
 
25
- def nil_subscription()
26
- return {
27
- 'eventFilters' => [],
28
- 'expirationTime' => '', # 2014-03-12T19:54:35.613Z
29
- 'expiresIn' => 0,
30
- 'deliveryMode' => {
31
- 'transportType' => 'PubNub',
32
- 'encryption' => false,
33
- 'address' => '',
34
- 'subscriberKey' => '',
35
- 'secretKey' => ''
36
- },
37
- 'id' => '',
38
- 'creationTime' => '', # 2014-03-12T19:54:35.613Z
39
- 'status' => '', # Active
40
- 'uri' => ''
41
- }
42
- end
29
+ def nil_subscription
30
+ {
31
+ 'eventFilters' => [],
32
+ 'expirationTime' => '', # 2014-03-12T19:54:35.613Z
33
+ 'expiresIn' => 0,
34
+ 'deliveryMode' => {
35
+ 'transportType' => 'PubNub',
36
+ 'encryption' => false,
37
+ 'address' => '',
38
+ 'subscriberKey' => '',
39
+ 'secretKey' => ''
40
+ },
41
+ 'id' => '',
42
+ 'creationTime' => '', # 2014-03-12T19:54:35.613Z
43
+ 'status' => '', # Active
44
+ 'uri' => ''
45
+ }
46
+ end
43
47
 
44
- def pubnub()
45
- return @_pubnub
46
- end
48
+ def pubnub
49
+ @_pubnub
50
+ end
47
51
 
48
- def register(events = nil)
49
- return alive? ? renew(events) : subscribe(events)
50
- end
52
+ def register(events = nil)
53
+ alive? ? renew(events) : subscribe(events)
54
+ end
51
55
 
52
- def add_events(events)
53
- unless events.is_a? Array
54
- raise 'Events is not an array.'
56
+ def add_events(events)
57
+ raise 'Events is not an array.' unless events.is_a? Array
58
+ @event_filters.push(events) unless events.empty?
55
59
  end
56
- @event_filters.push(events) if events.length > 0
57
- end
58
60
 
59
- def set_events(events)
60
- unless events.is_a? Array
61
- raise 'Events is not an array.'
61
+ def set_events(events)
62
+ raise 'Events is not an array.' unless events.is_a? Array
63
+ @event_filters = events
62
64
  end
63
- @event_filters = events
64
- end
65
65
 
66
- def subscribe(events=nil)
67
- set_events(events) if events.is_a? Array
66
+ def subscribe(events = nil)
67
+ set_events(events) if events.is_a? Array
68
68
 
69
- if !@event_filters.is_a?(Array) || @event_filters.length == 0
70
- raise 'Events are undefined'
71
- end
69
+ raise 'Events are undefined' unless @event_filters.is_a?(Array) && !@event_filters.empty?
72
70
 
73
- begin
74
- response = @_client.http.post do |req|
75
- req.url 'subscription'
76
- req.headers['Content-Type'] = 'application/json'
77
- req.body = {
78
- eventFilters: @_client.create_urls(@event_filters),
79
- deliveryMode: {
80
- transportType: 'PubNub'
71
+ begin
72
+ response = @_client.http.post do |req|
73
+ req.url 'subscription'
74
+ req.headers['Content-Type'] = 'application/json'
75
+ req.body = {
76
+ eventFilters: @_client.create_urls(@event_filters),
77
+ deliveryMode: { transportType: 'PubNub' }
81
78
  }
82
- }
79
+ end
80
+ set_subscription response.body
81
+ _subscribe_at_pubnub
82
+ changed
83
+ notify_observers response
84
+ return response
85
+ rescue StandardError => e
86
+ reset
87
+ changed
88
+ notify_observers(e)
89
+ raise 'Subscribe HTTP Request Error: ' + e.to_s
83
90
  end
84
- set_subscription response.body
85
- _subscribe_at_pubnub()
86
- changed
87
- notify_observers response
88
- return response
89
- rescue StandardError => e
90
- reset()
91
- changed
92
- notify_observers(e)
93
- raise 'Subscribe HTTP Request Error: ' + e.to_s
94
91
  end
95
- end
96
92
 
97
- def renew(events = nil)
98
- set_events(events) if events.is_a? Array
99
-
100
- unless alive?
101
- raise 'Subscription is not alive'
93
+ def renew(events = nil)
94
+ set_events(events) if events.is_a? Array
95
+
96
+ raise 'Subscription is not alive' unless alive?
97
+ raise 'Events are undefined' if @event_filters.empty?
98
+ _clear_timeout
99
+
100
+ begin
101
+ response = @_client.http.post do |req|
102
+ req.url uri_join(@_subscription['uri'], 'renew')
103
+ req.headers['Content-Type'] = 'application/json'
104
+ end
105
+
106
+ set_subscription response.body
107
+ changed
108
+ notify_observers response
109
+
110
+ return response
111
+ rescue StandardError => e
112
+ @client.logger.warn "RingCentralSdk::REST::Subscription: RENEW_ERROR #{e}"
113
+ reset
114
+ changed
115
+ notify_observers e
116
+ raise 'Renew HTTP Request Error'
117
+ end
102
118
  end
103
119
 
104
- if !@event_filters.is_a?(Array) || @event_filters.length ==0
105
- raise 'Events are undefined'
120
+ def remove
121
+ raise 'Subscription is not alive' unless alive?
122
+
123
+ begin
124
+ response = @_client.http.delete do |req|
125
+ req.url 'subscription/' + @_subscription['id'].to_s
126
+ end
127
+ reset
128
+ changed
129
+ notify_observers response.body
130
+ return response
131
+ rescue StandardError => e
132
+ reset
133
+ changed
134
+ notify_observers e
135
+ end
106
136
  end
107
- _clear_timeout
108
137
 
109
- begin
110
- response = @_client.http.post do |req|
111
- req.url uri_join(@_subscription['uri'], 'renew')
112
- req.headers['Content-Type'] = 'application/json'
138
+ def alive?
139
+ s = @_subscription
140
+ if
141
+ (s.key?('deliveryMode') && s['deliveryMode']) \
142
+ && (s['deliveryMode'].key?('subscriberKey') && s['deliveryMode']['subscriberKey']) \
143
+ && (
144
+ s['deliveryMode'].key?('address') \
145
+ && !s['deliveryMode']['address'].nil? \
146
+ && !s['deliveryMode']['address'].empty?
147
+ )
148
+ return true
113
149
  end
114
-
115
- set_subscription response.body
116
- changed
117
- notify_observers response
118
-
119
- return response
120
- rescue StandardError => e
121
- puts "RingCentralSdk::REST::Subscription: RENEW_ERROR #{e}"
122
- reset()
123
- changed
124
- notify_observers e
125
- raise 'Renew HTTP Request Error'
150
+ false
126
151
  end
127
- end
128
152
 
129
- def remove()
130
- unless alive?
131
- raise 'Subscription is not alive'
153
+ def subscription
154
+ @_subscription
132
155
  end
133
156
 
134
- begin
135
- response = @_client.http.delete do |req|
136
- req.url 'subscription/' + @_subscription['id'].to_s
137
- end
138
- reset()
139
- changed
140
- notify_observers response.body
141
- return response
142
- rescue StandardError => e
143
- reset()
144
- changed
145
- notify_observers e
157
+ def set_subscription(data)
158
+ _clear_timeout
159
+ @_subscription = data
160
+ _set_timeout
146
161
  end
147
- end
148
162
 
149
- def alive?
150
- s = @_subscription
151
- return (s.has_key?('deliveryMode') && s['deliveryMode']) && \
152
- (s['deliveryMode'].has_key?('subscriberKey') && s['deliveryMode']['subscriberKey']) && \
153
- (
154
- s['deliveryMode'].has_key?('address') && s['deliveryMode']['address'] && \
155
- s['deliveryMode']['address'].length>0) \
156
- ? true : false
157
- end
158
-
159
- def subscription
160
- return @_subscription
161
- end
163
+ def reset
164
+ _clear_timeout
165
+ _unsubscribe_at_pubnub
166
+ @_subscription = nil_subscription
167
+ end
162
168
 
163
- def set_subscription(data)
164
- _clear_timeout
165
- @_subscription = data
166
- _set_timeout
167
- end
169
+ def destroy
170
+ reset
171
+ end
168
172
 
169
- def reset
170
- _clear_timeout()
171
- _unsubscribe_at_pubnub()
172
- @_subscription = nil_subscription()
173
- end
173
+ def _subscribe_at_pubnub
174
+ raise 'Subscription is not alive' unless alive?
175
+
176
+ s_key = @_subscription['deliveryMode']['subscriberKey']
177
+
178
+ @_pubnub = new_pubnub(s_key, false, '')
179
+
180
+ callback = Pubnub::SubscribeCallback.new(
181
+ message: ->(envelope) {
182
+ @_client.logger.debug "MESSAGE: #{envelope.result[:data]}"
183
+ _notify envelope.result[:data][:message]
184
+ changed
185
+ },
186
+ presence: ->(envelope) {
187
+ @_client.logger.info "PRESENCE: #{envelope.result[:data]}"
188
+ },
189
+ status: lambda do |envelope|
190
+ @_client.logger.info "\n\n\n#{envelope.status}\n\n\n"
191
+ if envelope.error?
192
+ @_client.logger.info "ERROR! #{envelope.status[:category]}"
193
+ elsif envelope.status[:last_timetoken] == 0 # Connected!
194
+ @_client.logger.info('CONNECTED!')
195
+ end
196
+ end
197
+ )
198
+
199
+ @_pubnub.add_listener callback: callback, name: :ringcentral
200
+
201
+ @_pubnub.subscribe(
202
+ channels: @_subscription['deliveryMode']['address']
203
+ )
204
+ @_client.logger.debug('SUBSCRIBED')
205
+ end
174
206
 
175
- def destroy
176
- reset()
177
- end
207
+ def _notify(message)
208
+ count = count_observers
209
+ @_client.logger.debug("RingCentralSdk::REST::Subscription NOTIFYING '#{count}' observers")
178
210
 
179
- def _subscribe_at_pubnub
180
- unless alive?
181
- raise 'Subscription is not alive'
211
+ message = _decrypt message
212
+ changed
213
+ notify_observers message
182
214
  end
183
215
 
184
- s_key = @_subscription['deliveryMode']['subscriberKey']
185
-
186
- @_pubnub = new_pubnub(s_key, false, '')
216
+ def _decrypt(message)
217
+ unless alive?
218
+ raise 'Subscription is not alive'
219
+ end
187
220
 
188
- callback = lambda { |envelope|
189
- _notify(envelope.msg)
190
- changed
191
- }
221
+ if _encrypted?
222
+ delivery_mode = @_subscription['deliveryMode']
192
223
 
193
- @_pubnub.subscribe(
194
- channel: @_subscription['deliveryMode']['address'],
195
- callback: callback,
196
- error: lambda { |envelope| puts('ERROR: ' + envelope.msg.to_s) },
197
- connect: lambda { |envelope| puts('CONNECTED') },
198
- reconnect: lambda { |envelope| puts('RECONNECTED') },
199
- disconnect: lambda { |envelope| puts('DISCONNECTED') }
200
- )
201
- end
224
+ cipher = OpenSSL::Cipher::AES.new(128, :ECB)
225
+ cipher.decrypt
226
+ cipher.key = Base64.decode64(delivery_mode['encryptionKey'].to_s)
202
227
 
203
- def _notify(message)
204
- count = count_observers
205
- count_string = " -- RingCentralSdk::REST::Subscription: Notify #{count.to_s} observers"
206
- puts count_string
228
+ ciphertext = Base64.decode64(message)
229
+ plaintext = cipher.update(ciphertext) + cipher.final
207
230
 
208
- message = _decrypt message
209
- changed
210
- notify_observers message
211
- end
231
+ message = MultiJson.decode(plaintext, symbolize_keys: false)
232
+ end
212
233
 
213
- def _decrypt(message)
214
- unless alive?
215
- raise 'Subscription is not alive'
234
+ message
216
235
  end
217
236
 
218
- if _encrypted?()
237
+ def _encrypted?
219
238
  delivery_mode = @_subscription['deliveryMode']
220
-
221
- cipher = OpenSSL::Cipher::AES.new(128, :ECB)
222
- cipher.decrypt
223
- cipher.key = Base64.decode64(delivery_mode['encryptionKey'].to_s)
224
-
225
- ciphertext = Base64.decode64(message)
226
- plaintext = cipher.update(ciphertext) + cipher.final
227
-
228
- message = MultiJson.decode(plaintext, symbolize_keys: false)
239
+ is_encrypted = delivery_mode.key?('encryption') \
240
+ && delivery_mode['encryption'] \
241
+ && delivery_mode.key?('encryptionKey') \
242
+ && delivery_mode['encryptionKey']
243
+ is_encrypted
229
244
  end
230
245
 
231
- return message
232
- end
233
-
234
- def _encrypted?
235
- delivery_mode = @_subscription['deliveryMode']
236
- is_encrypted = delivery_mode.has_key?('encryption') && \
237
- delivery_mode['encryption'] && \
238
- delivery_mode.has_key?('encryptionKey') && \
239
- delivery_mode['encryptionKey']
240
- return is_encrypted
241
- end
242
-
243
- def _unsubscribe_at_pubnub
244
- if @_pubnub && alive?()
245
- @_pubnub.unsubscribe(channel: @_subscription['deliveryMode']['address']) do |envelope|
246
- # puts envelope.message
246
+ def _unsubscribe_at_pubnub
247
+ if @_pubnub && alive?
248
+ @_pubnub.unsubscribe(channel: @_subscription['deliveryMode']['address']) do |envelope|
249
+ puts envelope.status
250
+ end
247
251
  end
248
252
  end
249
- end
250
253
 
251
- def _set_timeout
252
- _clear_timeout
254
+ def _set_timeout
255
+ _clear_timeout
253
256
 
254
- time_to_expiration = @_subscription['expiresIn'] - RENEW_HANDICAP
257
+ time_to_expiration = @_subscription['expiresIn'] - RENEW_HANDICAP
255
258
 
256
- @_timeout = Thread.new do
257
- sleep time_to_expiration
258
- renew
259
+ @_timeout = Thread.new do
260
+ sleep time_to_expiration
261
+ renew
262
+ end
259
263
  end
260
- end
261
264
 
262
- def _clear_timeout
263
- @_timeout.exit if @_timeout.is_a?(Thread) && @_timeout.status == 'sleep'
264
- @_timeout = nil
265
- end
265
+ def _clear_timeout
266
+ @_timeout.exit if @_timeout.is_a?(Thread) && @_timeout.status == 'sleep'
267
+ @_timeout = nil
268
+ end
266
269
 
267
- def uri_join(*args)
268
- url = args.join('/').gsub(/\/+/, '/')
269
- return url.gsub(/^(https?:\/)/i, '\1/')
270
- end
270
+ def uri_join(*args)
271
+ url = args.join('/').gsub(%r{/+}, '/')
272
+ url.gsub(%r{^(https?:/)}i, '\1/')
273
+ end
271
274
 
272
- def new_pubnub(subscribe_key='', ssl_on=false, publish_key='', my_logger=nil)
273
- my_logger = Logger.new(STDOUT) if my_logger.nil?
274
-
275
- return Pubnub.new(
276
- subscribe_key: subscribe_key.to_s,
277
- publish_key: publish_key.to_s,
278
- error_callback: lambda { |msg|
279
- puts "Error callback says: #{msg.inspect}"
280
- },
281
- connect_callback: lambda { |msg|
282
- puts "CONNECTED: #{msg.inspect}"
283
- },
284
- logger: my_logger
285
- )
275
+ def new_pubnub(subscribe_key = '', ssl_on = false, publish_key = '', my_logger = nil)
276
+ Pubnub.new(
277
+ subscribe_key: subscribe_key.to_s,
278
+ publish_key: publish_key.to_s
279
+ )
280
+ end
286
281
  end
287
282
  end
288
283
  end