ribbon-intercom 0.2.3 → 0.3.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/lib/ribbon/intercom/client/mock_sdk.rb +13 -0
- data/lib/ribbon/intercom/client/sdk/adapters/adapter/response.rb +38 -0
- data/lib/ribbon/intercom/client/sdk/adapters/adapter.rb +51 -0
- data/lib/ribbon/intercom/client/sdk/adapters/http_adapter/connection.rb +34 -0
- data/lib/ribbon/intercom/client/sdk/adapters/http_adapter.rb +26 -0
- data/lib/ribbon/intercom/client/sdk/adapters/local_adapter.rb +54 -0
- data/lib/ribbon/intercom/client/sdk/adapters/mock_adapter.rb +40 -0
- data/lib/ribbon/intercom/client/sdk/adapters.rb +10 -0
- data/lib/ribbon/intercom/client/sdk.rb +71 -26
- data/lib/ribbon/intercom/client.rb +35 -7
- data/lib/ribbon/intercom/errors.rb +34 -6
- data/lib/ribbon/intercom/package.rb +64 -0
- data/lib/ribbon/intercom/packageable/mixin.rb +35 -0
- data/lib/ribbon/intercom/packageable.rb +6 -0
- data/lib/ribbon/intercom/service/channel/stores/mock_store.rb +40 -0
- data/lib/ribbon/intercom/service/channel/stores/redis_store.rb +186 -0
- data/lib/ribbon/intercom/service/{channel_stores/base.rb → channel/stores/store.rb} +13 -5
- data/lib/ribbon/intercom/service/channel/stores.rb +9 -0
- data/lib/ribbon/intercom/service/channel.rb +106 -4
- data/lib/ribbon/intercom/service.rb +156 -95
- data/lib/ribbon/intercom/utils/mixins/mock_safe.rb +26 -0
- data/lib/ribbon/intercom/utils/mixins.rb +5 -0
- data/lib/ribbon/intercom/utils/signer.rb +71 -0
- data/lib/ribbon/intercom/utils.rb +40 -13
- data/lib/ribbon/intercom/version.rb +1 -1
- data/lib/ribbon/intercom.rb +10 -7
- data/lib/tasks/intercom.rake +3 -3
- metadata +20 -20
- data/lib/ribbon/intercom/client/sdk/connection.rb +0 -35
- data/lib/ribbon/intercom/client/sdk/response.rb +0 -59
- data/lib/ribbon/intercom/service/channel_stores/redis_store.rb +0 -60
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 977c89158ba0646225543c4b65b86fc9db5663b4
|
4
|
+
data.tar.gz: 9482ff868c0d2e7974cbf08e329c8e18eb85b21a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6ac6cdc49ae1e738c21ad1cdc91a1d02340c7d04d35fe50a3eab1d9ca08e767366b3725b828b21f2e29b132a5bec70668683b337e80096e5b02e1c8f4063650f
|
7
|
+
data.tar.gz: ed7da55ea2c9dca75e712cf83f691977ed3aec4bb01f6ed0f5dcdf6999320d59b6dd091b954107f19cdaa7bc20985882c1293177e74f5f2e281720ca0080e5f0
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Ribbon::Intercom
|
2
|
+
class Client
|
3
|
+
class MockSDK < SDK
|
4
|
+
def initialize(service)
|
5
|
+
super(Adapters::MockAdapter.new(service))
|
6
|
+
end
|
7
|
+
|
8
|
+
def with_permissions(*perms, &block)
|
9
|
+
adapter.with_permissions(*perms, &block)
|
10
|
+
end
|
11
|
+
end # MockSDK
|
12
|
+
end # Client
|
13
|
+
end # Ribbon::Intercom
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Ribbon::Intercom
|
2
|
+
class Client::SDK::Adapters::Adapter
|
3
|
+
class Response < Rack::Response
|
4
|
+
attr_reader :method_name
|
5
|
+
|
6
|
+
def initialize(body, status, headers, method_name)
|
7
|
+
super(body, status, headers)
|
8
|
+
@method_name = method_name.to_sym
|
9
|
+
end
|
10
|
+
|
11
|
+
def missing_permissions
|
12
|
+
@__missing_permissions ||= _intercom_permissions_missing
|
13
|
+
end
|
14
|
+
|
15
|
+
def body
|
16
|
+
super.join
|
17
|
+
end
|
18
|
+
|
19
|
+
##
|
20
|
+
# Decode the response body received from the service.
|
21
|
+
def retval
|
22
|
+
@__retval ||= Marshal.load(Base64.strict_decode64(body)) unless body.empty?
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def _intercom_permissions_missing
|
28
|
+
key = _intercom_permissions_missing_header
|
29
|
+
headers[key] && headers[key].split(',').to_set
|
30
|
+
end
|
31
|
+
|
32
|
+
def _intercom_permissions_missing_header
|
33
|
+
missing_keys = ['X-Intercom-Permissions-Missing', :x_intercom_permissions_missing]
|
34
|
+
missing_keys.detect { |key| headers.key?(key) }
|
35
|
+
end
|
36
|
+
end # Response
|
37
|
+
end # Client::SDK::Adapters::Adapter
|
38
|
+
end # Ribbon::Intercom
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Ribbon::Intercom
|
2
|
+
module Client::SDK::Adapters
|
3
|
+
class Adapter
|
4
|
+
autoload(:Response, 'ribbon/intercom/client/sdk/adapters/adapter/response')
|
5
|
+
|
6
|
+
def initialize(*args)
|
7
|
+
connect(*args)
|
8
|
+
end
|
9
|
+
|
10
|
+
##
|
11
|
+
# Call the method on the service.
|
12
|
+
def call(method_name, *args)
|
13
|
+
call!(method_name, _encode_args(*args)).tap { |response|
|
14
|
+
raise TypeError, "call! should return an Adapter::Response" unless response.is_a?(Response)
|
15
|
+
raise "response should have correct method name" unless response.method_name == method_name.to_sym
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
def headers(h={})
|
20
|
+
(@__headers ||= {}).merge!(h)
|
21
|
+
end
|
22
|
+
|
23
|
+
##
|
24
|
+
# Connect to a service. The specific arguments depend on the Adapter
|
25
|
+
# subclass.
|
26
|
+
def connect(*args)
|
27
|
+
raise NotImplementedError
|
28
|
+
end
|
29
|
+
|
30
|
+
##
|
31
|
+
# Returns whether or not the Adapter is connected to a service.
|
32
|
+
def connected?
|
33
|
+
raise NotImplementedError
|
34
|
+
end
|
35
|
+
|
36
|
+
##
|
37
|
+
# Actually call the method on the service. Should return an Adapter::Response object.
|
38
|
+
def call!(method_name, *args)
|
39
|
+
raise NotImplementedError
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
##
|
45
|
+
# Encode the arguments for transmission to the service.
|
46
|
+
def _encode_args(*args)
|
47
|
+
Base64.strict_encode64(Marshal.dump(Utils.sanitize(args)))
|
48
|
+
end
|
49
|
+
end # Adapter
|
50
|
+
end # Client::SDK::Adapters
|
51
|
+
end # Ribbon::Intercom
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'openssl'
|
3
|
+
require 'uri'
|
4
|
+
|
5
|
+
module Ribbon::Intercom
|
6
|
+
module Client::SDK::Adapters
|
7
|
+
class HttpAdapter::Connection
|
8
|
+
attr_reader :url
|
9
|
+
|
10
|
+
def initialize(url, token, secret)
|
11
|
+
@url = url.is_a?(String) ? URI(url) : url
|
12
|
+
|
13
|
+
@_client = Net::HTTP.new(url.hostname, url.port)
|
14
|
+
@_client.use_ssl = url.is_a?(URI::HTTPS)
|
15
|
+
@_client.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
16
|
+
@_client.cert_store = OpenSSL::X509::Store.new.tap { |s| s.set_default_paths }
|
17
|
+
|
18
|
+
@_headers = {
|
19
|
+
'Authorization' => "Basic #{Base64.strict_encode64("#{token}:#{secret}")}"
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
23
|
+
def put(params={})
|
24
|
+
@_client.put(url.request_uri, params[:body], _prepare_headers(params[:headers]).merge(@_headers))
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def _prepare_headers(headers)
|
30
|
+
Hash[(headers || {}).map { |k, v| [k.to_s, v.to_s] }]
|
31
|
+
end
|
32
|
+
end # HttpAdapter::Connection
|
33
|
+
end # Client::SDK::Adapters
|
34
|
+
end # Ribbon::Intercom
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Ribbon::Intercom
|
2
|
+
module Client::SDK::Adapters
|
3
|
+
class HttpAdapter < Adapter
|
4
|
+
autoload(:Connection, 'ribbon/intercom/client/sdk/adapters/http_adapter/connection')
|
5
|
+
|
6
|
+
attr_reader :connection
|
7
|
+
|
8
|
+
def connect(*args)
|
9
|
+
@connection = Connection.new(*args)
|
10
|
+
end
|
11
|
+
|
12
|
+
def connected?
|
13
|
+
!!connection
|
14
|
+
end
|
15
|
+
|
16
|
+
def call!(method_name, encoded_args)
|
17
|
+
response = connection.put(
|
18
|
+
headers: headers.merge("X-Intercom-Method" => method_name),
|
19
|
+
body: encoded_args
|
20
|
+
)
|
21
|
+
|
22
|
+
Adapter::Response.new(response.body, response.code.to_i, response, method_name)
|
23
|
+
end
|
24
|
+
end # HttpAdapter
|
25
|
+
end # Client::SDK::Adapters
|
26
|
+
end # Ribbon::Intercom
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'base64'
|
2
|
+
|
3
|
+
module Ribbon::Intercom
|
4
|
+
module Client::SDK::Adapters
|
5
|
+
class LocalAdapter < Adapter
|
6
|
+
attr_reader :service
|
7
|
+
attr_accessor :channel_token
|
8
|
+
attr_accessor :channel_secret
|
9
|
+
|
10
|
+
def connect(service, params={})
|
11
|
+
case service
|
12
|
+
when Service
|
13
|
+
@service = service
|
14
|
+
else
|
15
|
+
raise ArgumentError, "Expected a service, got: #{service.inspect}"
|
16
|
+
end
|
17
|
+
|
18
|
+
@channel_token = params[:channel_token]
|
19
|
+
@channel_secret = params[:channel_secret]
|
20
|
+
end
|
21
|
+
|
22
|
+
def connected?(*args)
|
23
|
+
!!service
|
24
|
+
end
|
25
|
+
|
26
|
+
def call!(method_name, encoded_args)
|
27
|
+
response = service.call(
|
28
|
+
_http_headers.merge(
|
29
|
+
'HTTP_AUTHORIZATION' => _http_auth,
|
30
|
+
'HTTP_X_INTERCOM_METHOD' => method_name.to_s,
|
31
|
+
'REQUEST_METHOD' => 'PUT',
|
32
|
+
'rack.input' => StringIO.new(encoded_args)
|
33
|
+
)
|
34
|
+
)
|
35
|
+
|
36
|
+
Response.new(response[2], response[0], response[1], method_name)
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def _http_auth
|
42
|
+
"Basic #{Base64.strict_encode64("#{channel_token}:#{channel_secret}")}"
|
43
|
+
end
|
44
|
+
|
45
|
+
def _http_headers
|
46
|
+
Hash[headers.map { |name, value| [_header_to_http(name), value] }]
|
47
|
+
end
|
48
|
+
|
49
|
+
def _header_to_http(name)
|
50
|
+
name.to_s.upcase.gsub('-', '_')
|
51
|
+
end
|
52
|
+
end # MockAdapter
|
53
|
+
end # Client::SDK::Adapters
|
54
|
+
end # Ribbon::Intercom
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Ribbon::Intercom
|
2
|
+
module Client::SDK::Adapters
|
3
|
+
class MockAdapter < LocalAdapter
|
4
|
+
attr_reader :store
|
5
|
+
|
6
|
+
def connect(service)
|
7
|
+
if service.is_a?(Class) && service < Service
|
8
|
+
service = service.new(store: Service::Channel::Stores::MockStore.new)
|
9
|
+
end
|
10
|
+
|
11
|
+
unless service.is_a?(Service)
|
12
|
+
raise ArgumentError, "Expected a service, got: #{service.inspect}"
|
13
|
+
end
|
14
|
+
|
15
|
+
unless service.store.is_a?(Service::Channel::Stores::MockStore)
|
16
|
+
raise ArgumentError, "Expected service to have a MockStore, got: #{service.store.inspect}"
|
17
|
+
end
|
18
|
+
|
19
|
+
super(service)
|
20
|
+
@store = service.store
|
21
|
+
end
|
22
|
+
|
23
|
+
def with_permissions(*perms, &block)
|
24
|
+
channel = store.open_channel(name: 'mock channel', may: perms)
|
25
|
+
secret = channel.rotate_secret!
|
26
|
+
with_channel(channel.token, secret, &block)
|
27
|
+
ensure
|
28
|
+
channel.close
|
29
|
+
end
|
30
|
+
|
31
|
+
def with_channel(token, secret)
|
32
|
+
token_prv, secret_prv = self.channel_token, self.channel_secret
|
33
|
+
self.channel_token, self.channel_secret = token, secret
|
34
|
+
yield
|
35
|
+
ensure
|
36
|
+
self.channel_token, self.channel_secret = token_prv, secret_prv
|
37
|
+
end
|
38
|
+
end # MockAdapter
|
39
|
+
end # Client::SDK::Adapters
|
40
|
+
end # Ribbon::Intercom
|
@@ -0,0 +1,10 @@
|
|
1
|
+
module Ribbon::Intercom
|
2
|
+
class Client::SDK
|
3
|
+
module Adapters
|
4
|
+
autoload(:Adapter, 'ribbon/intercom/client/sdk/adapters/adapter')
|
5
|
+
autoload(:HttpAdapter, 'ribbon/intercom/client/sdk/adapters/http_adapter')
|
6
|
+
autoload(:LocalAdapter, 'ribbon/intercom/client/sdk/adapters/local_adapter')
|
7
|
+
autoload(:MockAdapter, 'ribbon/intercom/client/sdk/adapters/mock_adapter')
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
@@ -1,49 +1,94 @@
|
|
1
|
+
require 'base64'
|
2
|
+
|
1
3
|
module Ribbon::Intercom
|
2
4
|
class Client
|
3
5
|
class SDK
|
4
|
-
autoload(:
|
5
|
-
|
6
|
+
autoload(:Adapters, 'ribbon/intercom/client/sdk/adapters')
|
7
|
+
|
8
|
+
include Utils::Mixins::MockSafe
|
6
9
|
|
7
|
-
attr_reader :
|
10
|
+
attr_reader :adapter
|
8
11
|
|
9
12
|
def initialize(*args)
|
10
|
-
|
13
|
+
case args.first
|
14
|
+
when Adapters::Adapter
|
15
|
+
@adapter = args.first
|
16
|
+
else
|
17
|
+
@adapter = Adapters::HttpAdapter.new(*args)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def headers(h={})
|
22
|
+
adapter.headers(h)
|
11
23
|
end
|
12
24
|
|
13
25
|
def connect(*args)
|
14
|
-
|
26
|
+
adapter.connect(*args)
|
15
27
|
end
|
16
28
|
|
17
29
|
def connected?
|
18
|
-
|
30
|
+
adapter.connected?
|
19
31
|
end
|
20
32
|
|
21
|
-
|
22
|
-
|
33
|
+
##
|
34
|
+
# Calls the method on the adapter returning the Adapter::Response.
|
35
|
+
#
|
36
|
+
# Intended to be called by Package.
|
37
|
+
def call(method_name, *args)
|
38
|
+
_process_response(adapter.call(method_name, *args))
|
23
39
|
end
|
24
40
|
|
25
41
|
private
|
26
42
|
|
27
|
-
|
28
|
-
|
43
|
+
##
|
44
|
+
# Simulates calling the remote method as if it were a local method.
|
45
|
+
#
|
46
|
+
# Intended to be called by end-users.
|
47
|
+
def method_missing(meth, *args, &block)
|
48
|
+
call(meth, *args).retval
|
49
|
+
end
|
29
50
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
51
|
+
##
|
52
|
+
# Process an Adapter::Response object returned by Adapter#call.
|
53
|
+
def _process_response(response)
|
54
|
+
_handle_response_error(response) unless response.successful?
|
55
|
+
|
56
|
+
_init_packages(response.retval)
|
57
|
+
response
|
58
|
+
end
|
59
|
+
|
60
|
+
##
|
61
|
+
# Raises an error depending on what went wrong.
|
62
|
+
def _handle_response_error(response)
|
63
|
+
raise 'called for successful response' if response.successful?
|
64
|
+
|
65
|
+
encoded_error = response.headers['X-Intercom-Error']
|
66
|
+
|
67
|
+
if encoded_error && !encoded_error.empty?
|
68
|
+
begin
|
69
|
+
decoded_error = Base64.strict_decode64(encoded_error)
|
70
|
+
error = Marshal.load(decoded_error)
|
71
|
+
rescue
|
72
|
+
error = Errors::ServerError.new('unknown server error')
|
44
73
|
end
|
45
74
|
end
|
75
|
+
|
76
|
+
raise error || Errors::ServerError.new('unexpected server error')
|
77
|
+
end # _handle_response
|
78
|
+
|
79
|
+
##
|
80
|
+
# Walks the object and initializes all packages (i.e., sets them up to be
|
81
|
+
# accessed by the end-user on the client).
|
82
|
+
def _init_packages(object)
|
83
|
+
Utils.walk(object) { |object|
|
84
|
+
if object.is_a?(Package)
|
85
|
+
object.sdk = dup
|
86
|
+
object.mock_safe! if mock_safe?
|
87
|
+
end
|
88
|
+
|
89
|
+
object
|
90
|
+
}
|
46
91
|
end
|
47
|
-
end #
|
48
|
-
end #
|
92
|
+
end # SDK
|
93
|
+
end # Client
|
49
94
|
end # Ribbon::Intercom
|
@@ -3,6 +3,7 @@ require 'ribbon/config'
|
|
3
3
|
module Ribbon::Intercom
|
4
4
|
class Client
|
5
5
|
autoload(:SDK, 'ribbon/intercom/client/sdk')
|
6
|
+
autoload(:MockSDK, 'ribbon/intercom/client/mock_sdk')
|
6
7
|
|
7
8
|
def config(&block)
|
8
9
|
(@__config ||= Ribbon::Config.new).tap { |config|
|
@@ -16,16 +17,43 @@ module Ribbon::Intercom
|
|
16
17
|
_load_sdk(handle)
|
17
18
|
end
|
18
19
|
|
19
|
-
|
20
|
-
|
20
|
+
##
|
21
|
+
# Return a mock safe version of this client.
|
22
|
+
def mock_safe
|
23
|
+
dup.tap { |client|
|
24
|
+
client.preload_sdks
|
21
25
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
+
# Make SDK instances mock safe
|
27
|
+
client.sdk_instances = Hash[
|
28
|
+
client.sdk_instances.map { |service, sdk| [service, sdk.mock_safe] }
|
29
|
+
]
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
def preload_sdks
|
34
|
+
if config.service?
|
35
|
+
config.service.each { |name, *args|
|
36
|
+
_load_sdk(name)
|
37
|
+
}
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
protected
|
42
|
+
|
43
|
+
attr_accessor :sdk_instances
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def _load_sdk(handle)
|
48
|
+
raise Errors::ServiceNotDefinedError unless config.service?
|
49
|
+
|
50
|
+
(self.sdk_instances ||= Hash.new { |hash, key|
|
51
|
+
# Get the last service if there are duplicates
|
52
|
+
if (service_def = config.service.select { |r| r.first == key }.last)
|
53
|
+
name, config = service_def
|
26
54
|
hash[key] = SDK.new(config[:url], config[:token], config[:secret])
|
27
55
|
else
|
28
|
-
raise Errors::
|
56
|
+
raise Errors::ServiceNotDefinedError
|
29
57
|
end
|
30
58
|
})[handle.to_sym]
|
31
59
|
end
|
@@ -2,14 +2,44 @@ module Ribbon::Intercom
|
|
2
2
|
module Errors
|
3
3
|
class Error < StandardError; end
|
4
4
|
|
5
|
+
#############
|
6
|
+
# Http Errors
|
7
|
+
class HttpError < Error; end
|
8
|
+
|
9
|
+
##
|
10
|
+
# Request Errors
|
11
|
+
|
12
|
+
# 400 Error
|
13
|
+
class RequestError < HttpError; end
|
14
|
+
class UnsafeArgumentError < RequestError; end
|
15
|
+
class InvalidSubjectSignatureError < RequestError; end
|
16
|
+
|
17
|
+
# 401 Error
|
18
|
+
class AuthenticationError < RequestError; end
|
19
|
+
|
20
|
+
# 403 Error
|
21
|
+
class ForbiddenError < RequestError; end
|
22
|
+
class InsufficientPermissionsError < ForbiddenError; end
|
23
|
+
|
24
|
+
# 404 Not Found
|
25
|
+
class NotFoundError < RequestError; end
|
26
|
+
class InvalidMethodError < NotFoundError; end
|
27
|
+
|
28
|
+
# 405 Method Not Allowed
|
29
|
+
class MethodNotAllowedError < RequestError; end
|
30
|
+
|
31
|
+
##
|
32
|
+
# Server Errors
|
33
|
+
|
34
|
+
# 500 Error
|
35
|
+
class ServerError < HttpError; end
|
36
|
+
class UnsafeResponseError < ServerError; end
|
37
|
+
|
5
38
|
# General Errors
|
6
|
-
class MissingPermissionsError < Error; end
|
7
|
-
class ServerError < Error; end
|
8
39
|
class UnsafeValueError < Error; end
|
9
40
|
|
10
41
|
# Intercom Errors
|
11
|
-
class
|
12
|
-
class NoRemotesCreatedError < Error; end
|
42
|
+
class ServiceNotDefinedError < Error; end
|
13
43
|
|
14
44
|
# Service Errors
|
15
45
|
class NoPermissionsError < Error; end
|
@@ -22,8 +52,6 @@ module Ribbon::Intercom
|
|
22
52
|
|
23
53
|
# SDK Errors
|
24
54
|
class RequestFailureError < Error; end
|
25
|
-
class InvalidMethodError < Error; end
|
26
|
-
class AuthenticationError < Error; end
|
27
55
|
|
28
56
|
# Channel Errors
|
29
57
|
class ChannelNameMissingError < Error; end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'base64'
|
2
|
+
|
3
|
+
module Ribbon::Intercom
|
4
|
+
class Package
|
5
|
+
include Utils::Mixins::MockSafe
|
6
|
+
|
7
|
+
attr_reader :sdk
|
8
|
+
|
9
|
+
def initialize(subject_data=nil, data=nil)
|
10
|
+
self._subject_data = subject_data
|
11
|
+
@_data = data
|
12
|
+
end
|
13
|
+
|
14
|
+
def sdk=(sdk)
|
15
|
+
_set_sdk_headers(sdk)
|
16
|
+
@sdk = sdk
|
17
|
+
end
|
18
|
+
|
19
|
+
protected
|
20
|
+
|
21
|
+
def _subject_data=(data)
|
22
|
+
@_subject_data = data
|
23
|
+
_set_sdk_headers
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def method_missing(meth, *args, &block)
|
29
|
+
if @_data && @_data.key?(meth)
|
30
|
+
@_data[meth]
|
31
|
+
elsif sdk
|
32
|
+
_process_response(sdk.call(meth, *args))
|
33
|
+
else
|
34
|
+
super
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def marshal_dump
|
39
|
+
[@_subject_data, @_data]
|
40
|
+
end
|
41
|
+
|
42
|
+
def marshal_load(array)
|
43
|
+
self._subject_data = array[0]
|
44
|
+
@_data = array[1]
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
def _set_sdk_headers(sdk=sdk)
|
49
|
+
sdk.headers('X-Intercom-Subject' => @_subject_data) if sdk
|
50
|
+
end
|
51
|
+
|
52
|
+
def _process_response(response)
|
53
|
+
self._subject_data = response.headers['X-Intercom-Subject']
|
54
|
+
@_data = _retrieve_data_from_response(response)
|
55
|
+
|
56
|
+
response.retval
|
57
|
+
end
|
58
|
+
|
59
|
+
def _retrieve_data_from_response(response)
|
60
|
+
encoded_data = response.headers['X-Intercom-Package-Data']
|
61
|
+
encoded_data && Marshal.load(Base64.strict_decode64(encoded_data))
|
62
|
+
end
|
63
|
+
end # Package
|
64
|
+
end # Ribbon::Intercom
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module Ribbon::Intercom
|
4
|
+
class Packageable
|
5
|
+
module Mixin
|
6
|
+
class << self
|
7
|
+
def included(base)
|
8
|
+
base.extend(ClassMethods)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
def package_with(*args)
|
14
|
+
args.map { |m| _package_with_methods << m.to_sym }
|
15
|
+
end
|
16
|
+
|
17
|
+
def _package_with_methods
|
18
|
+
@__package_with_methods ||= [].to_set
|
19
|
+
end
|
20
|
+
end # ClassMethods
|
21
|
+
|
22
|
+
##
|
23
|
+
# Returns the package data for the instance as a hash.
|
24
|
+
def package_data
|
25
|
+
Utils.sanitize(
|
26
|
+
self.class._package_with_methods.map { |meth| [meth, public_send(meth)] }.to_h
|
27
|
+
)
|
28
|
+
end
|
29
|
+
|
30
|
+
def encoded_package_data
|
31
|
+
Base64.strict_encode64(Marshal.dump(package_data))
|
32
|
+
end
|
33
|
+
end # Mixin
|
34
|
+
end # Service::Subject
|
35
|
+
end # Ribbon::Intercom
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Ribbon::Intercom
|
2
|
+
class Service
|
3
|
+
module Channel::Stores
|
4
|
+
class MockStore < Store
|
5
|
+
def channels
|
6
|
+
@__channels ||= {}
|
7
|
+
end
|
8
|
+
|
9
|
+
def lock
|
10
|
+
@__lock ||= Mutex.new
|
11
|
+
end
|
12
|
+
|
13
|
+
def token_exists?(token)
|
14
|
+
channels.key?(token)
|
15
|
+
end
|
16
|
+
|
17
|
+
def lookup_channel(token)
|
18
|
+
channels[token]
|
19
|
+
end
|
20
|
+
|
21
|
+
def persist(channel)
|
22
|
+
raise Errors::InvalidChannelError, channel.inspect unless channel.is_a?(Channel)
|
23
|
+
channels[channel.token] = channel
|
24
|
+
end
|
25
|
+
|
26
|
+
def delete(channel)
|
27
|
+
raise Errors::InvalidChannelError, channel.inspect unless channel.is_a?(Channel)
|
28
|
+
channels.delete(channel.token)
|
29
|
+
nil
|
30
|
+
end
|
31
|
+
|
32
|
+
def with_lock(channel, &block)
|
33
|
+
# This is a global lock, not a per-channel lock, but this store is only
|
34
|
+
# for testing so let's KISS.
|
35
|
+
lock.synchronize(&block)
|
36
|
+
end
|
37
|
+
end # RedisStore
|
38
|
+
end # Channel::Stores
|
39
|
+
end # Service
|
40
|
+
end # Ribbon::Intercom
|