ably 1.1.2 → 1.1.6
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/check.yml +27 -0
- data/CHANGELOG.md +67 -0
- data/COPYRIGHT +1 -0
- data/LICENSE +172 -11
- data/MAINTAINERS.md +1 -0
- data/README.md +11 -21
- data/SPEC.md +1020 -922
- data/ably.gemspec +4 -4
- data/lib/ably/auth.rb +12 -2
- data/lib/ably/exceptions.rb +2 -2
- data/lib/ably/modules/ably.rb +11 -1
- data/lib/ably/realtime/channel.rb +7 -11
- data/lib/ably/realtime/channel/channel_manager.rb +2 -2
- data/lib/ably/realtime/channel/channel_properties.rb +24 -0
- data/lib/ably/realtime/client.rb +9 -0
- data/lib/ably/realtime/connection.rb +5 -4
- data/lib/ably/realtime/connection/websocket_transport.rb +67 -1
- data/lib/ably/realtime/presence.rb +0 -14
- data/lib/ably/rest/channel.rb +27 -19
- data/lib/ably/rest/client.rb +31 -15
- data/lib/ably/rest/middleware/fail_if_unsupported_mime_type.rb +4 -1
- data/lib/ably/version.rb +1 -1
- data/spec/acceptance/realtime/auth_spec.rb +3 -3
- data/spec/acceptance/realtime/channel_spec.rb +10 -0
- data/spec/acceptance/realtime/client_spec.rb +72 -16
- data/spec/acceptance/realtime/connection_failures_spec.rb +26 -11
- data/spec/acceptance/realtime/connection_spec.rb +36 -17
- data/spec/acceptance/realtime/presence_history_spec.rb +0 -58
- data/spec/acceptance/realtime/presence_spec.rb +54 -0
- data/spec/acceptance/realtime/push_admin_spec.rb +3 -19
- data/spec/acceptance/rest/auth_spec.rb +6 -75
- data/spec/acceptance/rest/base_spec.rb +8 -4
- data/spec/acceptance/rest/channel_spec.rb +42 -4
- data/spec/acceptance/rest/client_spec.rb +121 -26
- data/spec/acceptance/rest/push_admin_spec.rb +3 -19
- data/spec/shared/client_initializer_behaviour.rb +131 -8
- data/spec/spec_helper.rb +1 -0
- data/spec/support/serialization_helper.rb +21 -0
- data/spec/support/test_app.rb +2 -2
- data/spec/unit/realtime/client_spec.rb +19 -6
- metadata +20 -15
- data/.travis.yml +0 -19
@@ -89,31 +89,15 @@ describe Ably::Rest::Push::Admin do
|
|
89
89
|
end
|
90
90
|
end
|
91
91
|
|
92
|
-
def request_body(request, protocol)
|
93
|
-
if protocol == :msgpack
|
94
|
-
MessagePack.unpack(request.body)
|
95
|
-
else
|
96
|
-
JSON.parse(request.body)
|
97
|
-
end
|
98
|
-
end
|
99
|
-
|
100
|
-
def serialize(object, protocol)
|
101
|
-
if protocol == :msgpack
|
102
|
-
MessagePack.pack(object)
|
103
|
-
else
|
104
|
-
JSON.dump(object)
|
105
|
-
end
|
106
|
-
end
|
107
|
-
|
108
92
|
let!(:publish_stub) do
|
109
93
|
stub_request(:post, "#{client.endpoint}/push/publish").
|
110
94
|
with do |request|
|
111
|
-
expect(
|
112
|
-
expect(
|
95
|
+
expect(deserialize_body(request.body, protocol)['recipient']['camelCase']['secondLevelCamelCase']).to eql('val')
|
96
|
+
expect(deserialize_body(request.body, protocol)['recipient']).to_not have_key('camel_case')
|
113
97
|
true
|
114
98
|
end.to_return(
|
115
99
|
:status => 201,
|
116
|
-
:body =>
|
100
|
+
:body => serialize_body({}, protocol),
|
117
101
|
:headers => { 'Content-Type' => content_type }
|
118
102
|
)
|
119
103
|
end
|
@@ -69,14 +69,6 @@ shared_examples 'a client initializer' do
|
|
69
69
|
expect { subject }.to raise_error(ArgumentError, /key and key_name or key_secret are mutually exclusive/)
|
70
70
|
end
|
71
71
|
end
|
72
|
-
|
73
|
-
context 'client_id as only option' do
|
74
|
-
let(:client_options) { { client_id: 'valid' } }
|
75
|
-
|
76
|
-
it 'requires a valid key' do
|
77
|
-
expect { subject }.to raise_error(ArgumentError, /client_id cannot be provided without a complete API key or means to authenticate/)
|
78
|
-
end
|
79
|
-
end
|
80
72
|
end
|
81
73
|
|
82
74
|
context 'with valid arguments' do
|
@@ -273,6 +265,137 @@ shared_examples 'a client initializer' do
|
|
273
265
|
end
|
274
266
|
end
|
275
267
|
end
|
268
|
+
|
269
|
+
context 'environment' do
|
270
|
+
context 'when set without custom fallback hosts configured' do
|
271
|
+
let(:environment) { 'foo' }
|
272
|
+
let(:client_options) { default_options.merge(environment: environment) }
|
273
|
+
let(:default_fallbacks) { %w(a b c d e).map { |id| "#{environment}-#{id}-fallback.ably-realtime.com" } }
|
274
|
+
|
275
|
+
it 'sets the environment attribute' do
|
276
|
+
expect(subject.environment).to eql(environment)
|
277
|
+
end
|
278
|
+
|
279
|
+
it 'uses the default fallback hosts (#TBC, see https://github.com/ably/wiki/issues/361)' do
|
280
|
+
expect(subject.fallback_hosts.sort).to eql(default_fallbacks)
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
context 'when set with custom fallback hosts configured' do
|
285
|
+
let(:environment) { 'foo' }
|
286
|
+
let(:custom_fallbacks) { %w(a b c).map { |id| "#{environment}-#{id}.foo.com" } }
|
287
|
+
let(:client_options) { default_options.merge(environment: environment, fallback_hosts: custom_fallbacks) }
|
288
|
+
|
289
|
+
it 'sets the environment attribute' do
|
290
|
+
expect(subject.environment).to eql(environment)
|
291
|
+
end
|
292
|
+
|
293
|
+
it 'uses the custom provided fallback hosts (#RSC15a)' do
|
294
|
+
expect(subject.fallback_hosts.sort).to eql(custom_fallbacks)
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
context 'when set with fallback_hosts_use_default' do
|
299
|
+
let(:environment) { 'foo' }
|
300
|
+
let(:custom_fallbacks) { %w(a b c).map { |id| "#{environment}-#{id}.foo.com" } }
|
301
|
+
let(:default_production_fallbacks) { %w(a b c d e).map { |id| "#{id}.ably-realtime.com" } }
|
302
|
+
let(:client_options) { default_options.merge(environment: environment, fallback_hosts_use_default: true) }
|
303
|
+
|
304
|
+
it 'sets the environment attribute' do
|
305
|
+
expect(subject.environment).to eql(environment)
|
306
|
+
end
|
307
|
+
|
308
|
+
it 'uses the production default fallback hosts (#RTN17b)' do
|
309
|
+
expect(subject.fallback_hosts.sort).to eql(default_production_fallbacks)
|
310
|
+
end
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
context 'rest_host' do
|
315
|
+
context 'when set without custom fallback hosts configured' do
|
316
|
+
let(:custom_rest_host) { 'foo.com' }
|
317
|
+
let(:client_options) { default_options.merge(rest_host: custom_rest_host) }
|
318
|
+
|
319
|
+
it 'sets the custom_host attribute' do
|
320
|
+
expect(subject.custom_host).to eql(custom_rest_host)
|
321
|
+
end
|
322
|
+
|
323
|
+
it 'has no default fallback hosts' do
|
324
|
+
expect(subject.fallback_hosts).to be_empty
|
325
|
+
end
|
326
|
+
end
|
327
|
+
|
328
|
+
context 'when set with environment and without custom fallback hosts configured' do
|
329
|
+
let(:environment) { 'foobar' }
|
330
|
+
let(:custom_rest_host) { 'foo.com' }
|
331
|
+
let(:client_options) { default_options.merge(environment: environment, rest_host: custom_rest_host) }
|
332
|
+
|
333
|
+
it 'sets the environment attribute' do
|
334
|
+
expect(subject.environment).to eql(environment)
|
335
|
+
end
|
336
|
+
|
337
|
+
it 'sets the custom_host attribute' do
|
338
|
+
expect(subject.custom_host).to eql(custom_rest_host)
|
339
|
+
end
|
340
|
+
|
341
|
+
it 'has no default fallback hosts' do
|
342
|
+
expect(subject.fallback_hosts).to be_empty
|
343
|
+
end
|
344
|
+
end
|
345
|
+
|
346
|
+
context 'when set with custom fallback hosts configured' do
|
347
|
+
let(:custom_rest_host) { 'foo.com' }
|
348
|
+
let(:custom_fallbacks) { %w(a b c).map { |id| "#{environment}-#{id}.foo.com" } }
|
349
|
+
let(:client_options) { default_options.merge(rest_host: custom_rest_host, fallback_hosts: custom_fallbacks) }
|
350
|
+
|
351
|
+
it 'sets the custom_host attribute' do
|
352
|
+
expect(subject.custom_host).to eql(custom_rest_host)
|
353
|
+
end
|
354
|
+
|
355
|
+
it 'has no default fallback hosts' do
|
356
|
+
expect(subject.fallback_hosts.sort).to eql(custom_fallbacks)
|
357
|
+
end
|
358
|
+
end
|
359
|
+
end
|
360
|
+
|
361
|
+
context 'realtime_host' do
|
362
|
+
context 'when set without custom fallback hosts configured' do
|
363
|
+
let(:custom_realtime_host) { 'realtime.foo.com' }
|
364
|
+
let(:client_options) { default_options.merge(realtime_host: custom_realtime_host) }
|
365
|
+
|
366
|
+
# These tests are shared between realtime & rest clients
|
367
|
+
# So don't test for the attribute, instead test the options
|
368
|
+
it 'sets the realtime_host option' do
|
369
|
+
expect(subject.options[:realtime_host]).to eql(custom_realtime_host)
|
370
|
+
end
|
371
|
+
|
372
|
+
it 'has no default fallback hosts' do
|
373
|
+
expect(subject.fallback_hosts).to be_empty
|
374
|
+
end
|
375
|
+
end
|
376
|
+
end
|
377
|
+
|
378
|
+
context 'custom port' do
|
379
|
+
context 'when set without custom fallback hosts configured' do
|
380
|
+
let(:custom_port) { 555 }
|
381
|
+
let(:client_options) { default_options.merge(port: custom_port) }
|
382
|
+
|
383
|
+
it 'has no default fallback hosts' do
|
384
|
+
expect(subject.fallback_hosts).to be_empty
|
385
|
+
end
|
386
|
+
end
|
387
|
+
end
|
388
|
+
|
389
|
+
context 'custom TLS port' do
|
390
|
+
context 'when set without custom fallback hosts configured' do
|
391
|
+
let(:custom_port) { 555 }
|
392
|
+
let(:client_options) { default_options.merge(tls_port: custom_port) }
|
393
|
+
|
394
|
+
it 'has no default fallback hosts' do
|
395
|
+
expect(subject.fallback_hosts).to be_empty
|
396
|
+
end
|
397
|
+
end
|
398
|
+
end
|
276
399
|
end
|
277
400
|
|
278
401
|
context 'delegators' do
|
data/spec/spec_helper.rb
CHANGED
@@ -19,6 +19,7 @@ require 'support/event_emitter_helper'
|
|
19
19
|
require 'support/private_api_formatter'
|
20
20
|
require 'support/protocol_helper'
|
21
21
|
require 'support/random_helper'
|
22
|
+
require 'support/serialization_helper'
|
22
23
|
require 'support/test_logger_helper'
|
23
24
|
|
24
25
|
require 'rspec_config'
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module SerializationHelper
|
2
|
+
def serialize_body(object, protocol)
|
3
|
+
if protocol == :msgpack
|
4
|
+
MessagePack.pack(object)
|
5
|
+
else
|
6
|
+
JSON.dump(object)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def deserialize_body(object, protocol)
|
11
|
+
if protocol == :msgpack
|
12
|
+
MessagePack.unpack(object)
|
13
|
+
else
|
14
|
+
JSON.parse(object)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
RSpec.configure do |config|
|
19
|
+
config.include self
|
20
|
+
end
|
21
|
+
end
|
data/spec/support/test_app.rb
CHANGED
@@ -4,11 +4,11 @@ class TestApp
|
|
4
4
|
TEST_RESOURCES_PATH = File.expand_path('../../../lib/submodules/ably-common/test-resources', __FILE__)
|
5
5
|
|
6
6
|
# App configuration for test app
|
7
|
-
# See https://github.com/ably/ably-common/blob/
|
7
|
+
# See https://github.com/ably/ably-common/blob/main/test-resources/test-app-setup.json
|
8
8
|
APP_SPEC = JSON.parse(File.read(File.join(TEST_RESOURCES_PATH, 'test-app-setup.json')))['post_apps']
|
9
9
|
|
10
10
|
# Cipher details used for client_encoded presence data in test app
|
11
|
-
# See https://github.com/ably/ably-common/blob/
|
11
|
+
# See https://github.com/ably/ably-common/blob/main/test-resources/test-app-setup.json
|
12
12
|
APP_SPEC_CIPHER = JSON.parse(File.read(File.join(TEST_RESOURCES_PATH, 'test-app-setup.json')))['cipher']
|
13
13
|
|
14
14
|
# If an app has already been created and we need a new app, create a new test app
|
@@ -3,7 +3,7 @@ require 'spec_helper'
|
|
3
3
|
require 'shared/client_initializer_behaviour'
|
4
4
|
|
5
5
|
describe Ably::Realtime::Client do
|
6
|
-
subject do
|
6
|
+
subject(:realtime_client) do
|
7
7
|
Ably::Realtime::Client.new(client_options)
|
8
8
|
end
|
9
9
|
|
@@ -15,28 +15,41 @@ describe Ably::Realtime::Client do
|
|
15
15
|
it 'passes on the options to the initializer' do
|
16
16
|
rest_client = instance_double('Ably::Rest::Client', auth: instance_double('Ably::Auth'), options: client_options, environment: 'production', use_tls?: true, custom_tls_port: nil)
|
17
17
|
expect(Ably::Rest::Client).to receive(:new).with(hash_including(client_options)).and_return(rest_client)
|
18
|
-
|
18
|
+
realtime_client
|
19
19
|
end
|
20
20
|
|
21
21
|
context 'for attribute' do
|
22
22
|
[:environment, :use_tls?, :log_level, :custom_host].each do |attribute|
|
23
23
|
specify "##{attribute}" do
|
24
|
-
expect(
|
25
|
-
|
24
|
+
expect(realtime_client.rest_client).to receive(attribute)
|
25
|
+
realtime_client.public_send attribute
|
26
26
|
end
|
27
27
|
end
|
28
28
|
end
|
29
29
|
end
|
30
30
|
|
31
|
+
context 'when :transport_params option is passed' do
|
32
|
+
let(:expected_transport_params) do
|
33
|
+
{ 'heartbeats' => 'true', 'v' => '1.0', 'extra_param' => 'extra_param' }
|
34
|
+
end
|
35
|
+
let(:client_options) do
|
36
|
+
{ key: 'appid.keyuid:keysecret', transport_params: { heartbeats: true, v: 1.0, extra_param: 'extra_param'} }
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'converts options to strings' do
|
40
|
+
expect(realtime_client.transport_params).to eq(expected_transport_params)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
31
44
|
context 'push' do
|
32
45
|
let(:client_options) { { key: 'appid.keyuid:keysecret' } }
|
33
46
|
|
34
47
|
specify '#device is not supported and raises an exception' do
|
35
|
-
expect {
|
48
|
+
expect { realtime_client.device }.to raise_error Ably::Exceptions::PushNotificationsNotSupported
|
36
49
|
end
|
37
50
|
|
38
51
|
specify '#push returns a Push object' do
|
39
|
-
expect(
|
52
|
+
expect(realtime_client.push).to be_a(Ably::Realtime::Push)
|
40
53
|
end
|
41
54
|
end
|
42
55
|
|
metadata
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ably
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.1.
|
4
|
+
version: 1.1.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Lewis Marshall
|
8
8
|
- Matthew O'Riordan
|
9
|
-
autorequire:
|
9
|
+
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2021-05-25 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: eventmachine
|
@@ -45,42 +45,42 @@ dependencies:
|
|
45
45
|
requirements:
|
46
46
|
- - "~>"
|
47
47
|
- !ruby/object:Gem::Version
|
48
|
-
version:
|
48
|
+
version: '7.4'
|
49
49
|
type: :runtime
|
50
50
|
prerelease: false
|
51
51
|
version_requirements: !ruby/object:Gem::Requirement
|
52
52
|
requirements:
|
53
53
|
- - "~>"
|
54
54
|
- !ruby/object:Gem::Version
|
55
|
-
version:
|
55
|
+
version: '7.4'
|
56
56
|
- !ruby/object:Gem::Dependency
|
57
57
|
name: faraday
|
58
58
|
requirement: !ruby/object:Gem::Requirement
|
59
59
|
requirements:
|
60
60
|
- - "~>"
|
61
61
|
- !ruby/object:Gem::Version
|
62
|
-
version: '0
|
62
|
+
version: '1.0'
|
63
63
|
type: :runtime
|
64
64
|
prerelease: false
|
65
65
|
version_requirements: !ruby/object:Gem::Requirement
|
66
66
|
requirements:
|
67
67
|
- - "~>"
|
68
68
|
- !ruby/object:Gem::Version
|
69
|
-
version: '0
|
69
|
+
version: '1.0'
|
70
70
|
- !ruby/object:Gem::Dependency
|
71
|
-
name:
|
71
|
+
name: typhoeus
|
72
72
|
requirement: !ruby/object:Gem::Requirement
|
73
73
|
requirements:
|
74
74
|
- - "~>"
|
75
75
|
- !ruby/object:Gem::Version
|
76
|
-
version: '
|
76
|
+
version: '1.4'
|
77
77
|
type: :runtime
|
78
78
|
prerelease: false
|
79
79
|
version_requirements: !ruby/object:Gem::Requirement
|
80
80
|
requirements:
|
81
81
|
- - "~>"
|
82
82
|
- !ruby/object:Gem::Version
|
83
|
-
version: '
|
83
|
+
version: '1.4'
|
84
84
|
- !ruby/object:Gem::Dependency
|
85
85
|
name: json
|
86
86
|
requirement: !ruby/object:Gem::Requirement
|
@@ -241,14 +241,14 @@ dependencies:
|
|
241
241
|
requirements:
|
242
242
|
- - "~>"
|
243
243
|
- !ruby/object:Gem::Version
|
244
|
-
version: '
|
244
|
+
version: '3.11'
|
245
245
|
type: :development
|
246
246
|
prerelease: false
|
247
247
|
version_requirements: !ruby/object:Gem::Requirement
|
248
248
|
requirements:
|
249
249
|
- - "~>"
|
250
250
|
- !ruby/object:Gem::Version
|
251
|
-
version: '
|
251
|
+
version: '3.11'
|
252
252
|
- !ruby/object:Gem::Dependency
|
253
253
|
name: coveralls
|
254
254
|
requirement: !ruby/object:Gem::Requirement
|
@@ -314,13 +314,15 @@ extensions: []
|
|
314
314
|
extra_rdoc_files: []
|
315
315
|
files:
|
316
316
|
- ".editorconfig"
|
317
|
+
- ".github/workflows/check.yml"
|
317
318
|
- ".gitignore"
|
318
319
|
- ".gitmodules"
|
319
320
|
- ".rspec"
|
320
|
-
- ".travis.yml"
|
321
321
|
- CHANGELOG.md
|
322
|
+
- COPYRIGHT
|
322
323
|
- Gemfile
|
323
324
|
- LICENSE
|
325
|
+
- MAINTAINERS.md
|
324
326
|
- README.md
|
325
327
|
- Rakefile
|
326
328
|
- SPEC.md
|
@@ -377,6 +379,7 @@ files:
|
|
377
379
|
- lib/ably/realtime/auth.rb
|
378
380
|
- lib/ably/realtime/channel.rb
|
379
381
|
- lib/ably/realtime/channel/channel_manager.rb
|
382
|
+
- lib/ably/realtime/channel/channel_properties.rb
|
380
383
|
- lib/ably/realtime/channel/channel_state_machine.rb
|
381
384
|
- lib/ably/realtime/channel/publisher.rb
|
382
385
|
- lib/ably/realtime/channel/push_channel.rb
|
@@ -460,6 +463,7 @@ files:
|
|
460
463
|
- spec/support/protocol_helper.rb
|
461
464
|
- spec/support/random_helper.rb
|
462
465
|
- spec/support/rest_testapp_before_retry.rb
|
466
|
+
- spec/support/serialization_helper.rb
|
463
467
|
- spec/support/test_app.rb
|
464
468
|
- spec/support/test_logger_helper.rb
|
465
469
|
- spec/unit/auth_spec.rb
|
@@ -512,7 +516,7 @@ homepage: http://github.com/ably/ably-ruby
|
|
512
516
|
licenses:
|
513
517
|
- Apache-2.0
|
514
518
|
metadata: {}
|
515
|
-
post_install_message:
|
519
|
+
post_install_message:
|
516
520
|
rdoc_options: []
|
517
521
|
require_paths:
|
518
522
|
- lib
|
@@ -528,7 +532,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
528
532
|
version: '0'
|
529
533
|
requirements: []
|
530
534
|
rubygems_version: 3.0.3
|
531
|
-
signing_key:
|
535
|
+
signing_key:
|
532
536
|
specification_version: 4
|
533
537
|
summary: A Ruby client library for ably.io realtime messaging implemented using EventMachine
|
534
538
|
test_files:
|
@@ -574,6 +578,7 @@ test_files:
|
|
574
578
|
- spec/support/protocol_helper.rb
|
575
579
|
- spec/support/random_helper.rb
|
576
580
|
- spec/support/rest_testapp_before_retry.rb
|
581
|
+
- spec/support/serialization_helper.rb
|
577
582
|
- spec/support/test_app.rb
|
578
583
|
- spec/support/test_logger_helper.rb
|
579
584
|
- spec/unit/auth_spec.rb
|
data/.travis.yml
DELETED
@@ -1,19 +0,0 @@
|
|
1
|
-
sudo: false
|
2
|
-
env:
|
3
|
-
- RSPEC_RETRY=true PARALLEL_TEST_PROCESSORS=2 PROTOCOL=json
|
4
|
-
- RSPEC_RETRY=true PARALLEL_TEST_PROCESSORS=2 PROTOCOL=msgpack
|
5
|
-
language: ruby
|
6
|
-
rvm:
|
7
|
-
- 1.9.3
|
8
|
-
- 2.1.10
|
9
|
-
- 2.2.10
|
10
|
-
- 2.3.8
|
11
|
-
- 2.5.5
|
12
|
-
- 2.6.0
|
13
|
-
- 2.6.1
|
14
|
-
- 2.6.2
|
15
|
-
- 2.6.3
|
16
|
-
script: spec/run_parallel_tests
|
17
|
-
notifications:
|
18
|
-
slack:
|
19
|
-
secure: Xe8MwDcV2C8XLGk6O6Co31LpQiRSxsmS7Toy5vM7rHds5fnVRBNn5iX6Q5mXMdLOlnsMhjKLt7zl4fsBOZv+siQ+Us0omZSIYpXCYSCIj8nofReF0Lj8M4oa6lFSL5OuygO7PH+wLKTRxQURGZ6Pi1nHU+RE5izRmsewQHkhtY0=
|