dodgeball-trust-sdk-ruby 0.0.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: abfd89964ce453124544cf58b89603d36c7ae5286aa52998a53e55d2755d67a5
4
+ data.tar.gz: 9e04f8b5b691b617d1e1e65df1e613e780023fc75ffe62a0d9b20a28a0450ad3
5
+ SHA512:
6
+ metadata.gz: 5eed501cfcf9f9d47eaa5f4d1469b2e14ff876212abd012941c2a22634c34986f25a1de91963dfcba8b923ede71752d9923bf6a6c0bfe668e3b597207832e55f
7
+ data.tar.gz: ded1407e1a0bdb6c530d1650fb8f394ec6f44219b90eaa4a43613d6a05eac2ed01e1610c0e8e23a1a3fca746e88ea8a9215ee06bf67485deb0cb131733320027
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dodgeball/client/defaults'
4
+
5
+ module Dodgeball
6
+ class Client
7
+ class BackoffPolicy
8
+ include Dodgeball::Client::Defaults::BackoffPolicy
9
+
10
+ # @param [Hash] opts
11
+ # @option opts [Numeric] :min_timeout_ms The minimum backoff timeout
12
+ # @option opts [Numeric] :max_timeout_ms The maximum backoff timeout
13
+ # @option opts [Numeric] :multiplier The value to multiply the current
14
+ # interval with for each retry attempt
15
+ # @option opts [Numeric] :randomization_factor The randomization factor
16
+ # to use to create a range around the retry interval
17
+ def initialize(opts = {})
18
+ @min_timeout_ms = opts[:min_timeout_ms] || MIN_TIMEOUT_MS
19
+ @max_timeout_ms = opts[:max_timeout_ms] || MAX_TIMEOUT_MS
20
+ @multiplier = opts[:multiplier] || MULTIPLIER
21
+ @randomization_factor = opts[:randomization_factor] || RANDOMIZATION_FACTOR
22
+
23
+ @attempts = 0
24
+ end
25
+
26
+ # @return [Numeric] the next backoff interval, in milliseconds.
27
+ def next_interval
28
+ interval = @min_timeout_ms * (@multiplier**@attempts)
29
+ interval = add_jitter(interval, @randomization_factor)
30
+
31
+ @attempts += 1
32
+
33
+ [interval, @max_timeout_ms].min
34
+ end
35
+
36
+ private
37
+
38
+ def add_jitter(base, randomization_factor)
39
+ random_number = rand
40
+ max_deviation = base * randomization_factor
41
+ deviation = random_number * max_deviation
42
+
43
+ if random_number < 0.5
44
+ base - deviation
45
+ else
46
+ base + deviation
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dodgeball
4
+ class Client
5
+ module Defaults
6
+ module Request
7
+ # HOST/PORT Used for testing
8
+ HOST = 'localhost'.freeze
9
+ PORT = 3000.freeze
10
+ PATH_ROOT = '/v1/'.freeze
11
+ DODGEBALL_API_URL = 'http://localhost:3000'.freeze
12
+
13
+ DEFAULT_READ_TIMEOUT = 8.freeze
14
+ DEFAULT_OPEN_TIMEOUT = 4.freeze
15
+ SSL = true.freeze
16
+ HEADERS = { 'Accept' => 'application/json',
17
+ 'Content-Type' => 'application/json',
18
+ 'User-Agent' => "dodgeball-ruby/#{Client::VERSION}" }.freeze
19
+ RETRIES = 10.freeze
20
+
21
+ SECRET_KEY_HEADER = 'Dodgeball-Secret-Key'.freeze
22
+ SOURCE_ID_HEADER = 'Dodgeball-Source-Id'.freeze
23
+ VERIFICATION_ID_HEADER = 'Dodgeball-Verification-Id'.freeze
24
+ end
25
+
26
+ module BackoffPolicy
27
+ MIN_TIMEOUT_MS = 100.freeze
28
+ MAX_TIMEOUT_MS = 10000.freeze
29
+ MULTIPLIER = 1.5.freeze
30
+ RANDOMIZATION_FACTOR = 0.5.freeze
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'logger'
4
+
5
+ module Dodgeball
6
+ class Client
7
+ # Wraps an existing logger and adds a prefix to all messages
8
+ class PrefixedLogger
9
+ def initialize(logger, prefix)
10
+ @logger = logger
11
+ @prefix = prefix
12
+ end
13
+
14
+ def debug(msg)
15
+ @logger.debug("#{@prefix} #{msg}")
16
+ end
17
+
18
+ def info(msg)
19
+ @logger.info("#{@prefix} #{msg}")
20
+ end
21
+
22
+ def warn(msg)
23
+ @logger.warn("#{@prefix} #{msg}")
24
+ end
25
+
26
+ def error(msg)
27
+ @logger.error("#{@prefix} #{msg}")
28
+ end
29
+ end
30
+
31
+ module Logging
32
+ class << self
33
+ def logger
34
+ return @logger if @logger
35
+
36
+ base_logger = if defined?(Rails)
37
+ Rails.logger
38
+ else
39
+ logger = Logger.new STDOUT
40
+ logger.progname = 'Dodgeball::Client'
41
+ logger
42
+ end
43
+ @logger = PrefixedLogger.new(base_logger, '[dodgeball-ruby]')
44
+ end
45
+
46
+ attr_writer :logger
47
+ end
48
+
49
+ def self.included(base)
50
+ class << base
51
+ def logger
52
+ Logging.logger
53
+ end
54
+ end
55
+ end
56
+
57
+ def logger
58
+ Logging.logger
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,143 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dodgeball/client/defaults'
4
+ require 'dodgeball/client/utils'
5
+ require 'dodgeball/client/response'
6
+ require 'dodgeball/client/logging'
7
+ require 'dodgeball/client/backoff_policy'
8
+ require 'net/http'
9
+ require 'net/https'
10
+ require 'json'
11
+ require 'pry'
12
+ require 'uri'
13
+
14
+ module Dodgeball
15
+ class Client
16
+ class Request
17
+ include Dodgeball::Client::Defaults::Request
18
+ include Dodgeball::Client::Utils
19
+ include Dodgeball::Client::Logging
20
+
21
+
22
+ # public: Creates a new request object to send dodgeball api request
23
+ #
24
+ def initialize(options = {})
25
+ options[:host] ||= HOST
26
+ options[:port] ||= PORT
27
+
28
+ options[:ssl] ||= SSL
29
+
30
+ @headers = options[:headers] || HEADERS
31
+ @retries = options[:retries] || RETRIES
32
+ @backoff_policy =
33
+ options[:backoff_policy] || Dodgeball::Client::BackoffPolicy.new
34
+
35
+ uri = URI(options[:dodgeball_api_url] || DODGEBALL_API_URL)
36
+
37
+ http = Net::HTTP.new(uri.host, uri.port)
38
+ http.use_ssl = options[:ssl]
39
+ http.read_timeout = DEFAULT_READ_TIMEOUT
40
+ http.open_timeout = DEFAULT_OPEN_TIMEOUT
41
+
42
+ @http = http
43
+ end
44
+
45
+
46
+ # public: Posts the write key and path to the API.
47
+ #
48
+ # returns - Response of the status and error if it exists
49
+ def post(write_key, path, request_body, request_specific_headers=nil)
50
+
51
+ last_response, exception = retry_with_backoff(@retries) do
52
+ status_code, response_body = send_request(write_key, path, request_body, request_specific_headers)
53
+ should_retry = should_retry_request?(status_code, response_body)
54
+ logger.debug("Response status code: #{status_code}")
55
+ logger.debug("Response response body: #{response_body}") if response_body
56
+
57
+ [Response.new(status_code, response_body), should_retry]
58
+ end
59
+
60
+ if exception
61
+ logger.error(exception.message)
62
+ puts "E #{exception.message}"
63
+ exception.backtrace.each { |line| logger.error(line) }
64
+ Response.new(-1, exception.to_s)
65
+ else
66
+ last_response
67
+ end
68
+ end
69
+
70
+ private
71
+
72
+ def should_retry_request?(status_code, body)
73
+ if status_code >= 500
74
+ true # Server error
75
+ elsif status_code == 429
76
+ true # Rate limited
77
+ elsif status_code >= 400
78
+ logger.error(body)
79
+ false # Client error. Do not retry, but log
80
+ else
81
+ false
82
+ end
83
+ end
84
+
85
+ # Takes a block that returns [result, should_retry].
86
+ #
87
+ # Retries upto `retries_remaining` times, if `should_retry` is false or
88
+ # an exception is raised. `@backoff_policy` is used to determine the
89
+ # duration to sleep between attempts
90
+ #
91
+ # Returns [last_result, raised_exception]
92
+ def retry_with_backoff(retries_remaining, &block)
93
+ result, caught_exception = nil
94
+ should_retry = false
95
+
96
+ begin
97
+ result, should_retry = yield
98
+ return [result, nil] unless should_retry
99
+ rescue StandardError => e
100
+ should_retry = true
101
+ caught_exception = e
102
+ end
103
+
104
+ if should_retry && (retries_remaining > 1)
105
+ logger.debug("Retrying request, #{retries_remaining} retries left")
106
+ sleep(@backoff_policy.next_interval.to_f / 1000)
107
+ retry_with_backoff(retries_remaining - 1, &block)
108
+ else
109
+ [result, caught_exception]
110
+ end
111
+ end
112
+
113
+ # Sends a request to the path, returns [status_code, body]
114
+ def send_request(write_key, path, body, request_specific_headers)
115
+
116
+ payload = JSON.generate(body) if body
117
+
118
+ request_headers =(@headers || {}).merge(request_specific_headers || {})
119
+ request_headers[SECRET_KEY_HEADER] = write_key
120
+ request = Net::HTTP::Post.new(path, request_headers)
121
+
122
+ if self.class.stub
123
+ logger.debug "stubbed request to #{path}: " \
124
+ "write key = #{write_key}, payload = #{payload}"
125
+
126
+ [200, '{}']
127
+ else
128
+ puts payload
129
+ response = @http.request(request, payload)
130
+ [response.code.to_i, response.body]
131
+ end
132
+ end
133
+
134
+ class << self
135
+ attr_writer :stub
136
+
137
+ def stub
138
+ @stub || ENV['STUB']
139
+ end
140
+ end
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dodgeball
4
+ class Client
5
+ class Response
6
+ attr_reader :status, :response_body
7
+
8
+ # public: Simple class to wrap responses from the API
9
+ #
10
+ #
11
+ def initialize(status = 200, response_body = nil)
12
+ @status = status
13
+ @response_body = response_body
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+
5
+ module Dodgeball
6
+ class Client
7
+ module Utils
8
+ extend self
9
+
10
+ # public: Return a new hash with keys converted from strings to symbols
11
+ #
12
+ def symbolize_keys(hash)
13
+ hash.each_with_object({}) do |(k, v), memo|
14
+ memo[k.to_sym] = v
15
+ end
16
+ end
17
+
18
+ # public: Convert hash keys from strings to symbols in place
19
+ #
20
+ def symbolize_keys!(hash)
21
+ hash.replace symbolize_keys hash
22
+ end
23
+
24
+ # public: Return a new hash with keys as strings
25
+ #
26
+ def stringify_keys(hash)
27
+ hash.each_with_object({}) do |(k, v), memo|
28
+ memo[k.to_s] = v
29
+ end
30
+ end
31
+
32
+ # public: Returns a new hash with all the date values in the into iso8601
33
+ # strings
34
+ #
35
+ def isoify_dates(hash)
36
+ hash.each_with_object({}) do |(k, v), memo|
37
+ memo[k] = datetime_in_iso8601(v)
38
+ end
39
+ end
40
+
41
+ # public: Converts all the date values in the into iso8601 strings in place
42
+ #
43
+ def isoify_dates!(hash)
44
+ hash.replace isoify_dates hash
45
+ end
46
+
47
+ # public: Returns a uid string
48
+ #
49
+ def uid
50
+ arr = SecureRandom.random_bytes(16).unpack('NnnnnN')
51
+ arr[2] = (arr[2] & 0x0fff) | 0x4000
52
+ arr[3] = (arr[3] & 0x3fff) | 0x8000
53
+ '%08x-%04x-%04x-%04x-%04x%08x' % arr
54
+ end
55
+
56
+ def datetime_in_iso8601(datetime)
57
+ case datetime
58
+ when Time
59
+ time_in_iso8601 datetime
60
+ when DateTime
61
+ time_in_iso8601 datetime.to_time
62
+ when Date
63
+ date_in_iso8601 datetime
64
+ else
65
+ datetime
66
+ end
67
+ end
68
+
69
+ def time_in_iso8601(time, fraction_digits = 3)
70
+ fraction = (('.%06i' % time.usec)[0, fraction_digits + 1] if fraction_digits > 0)
71
+
72
+ "#{time.strftime('%Y-%m-%dT%H:%M:%S')}#{fraction}#{formatted_offset(time, true, 'Z')}"
73
+ end
74
+
75
+ def date_in_iso8601(date)
76
+ date.strftime('%F')
77
+ end
78
+
79
+ def formatted_offset(time, colon = true, alternate_utc_string = nil)
80
+ time.utc? && alternate_utc_string || seconds_to_utc_offset(time.utc_offset, colon)
81
+ end
82
+
83
+ def seconds_to_utc_offset(seconds, colon = true)
84
+ (colon ? UTC_OFFSET_WITH_COLON : UTC_OFFSET_WITHOUT_COLON) % [(seconds < 0 ? '-' : '+'), (seconds.abs / 3600), ((seconds.abs % 3600) / 60)]
85
+ end
86
+
87
+ UTC_OFFSET_WITH_COLON = '%s%02d:%02d'
88
+ UTC_OFFSET_WITHOUT_COLON = UTC_OFFSET_WITH_COLON.sub(':', '')
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dodgeball
4
+ class Client
5
+ VERSION = '0.0.1'
6
+ end
7
+ end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'time'
4
+ require 'uri'
5
+
6
+ require 'dodgeball/client/version'
7
+ require 'dodgeball/client/defaults'
8
+ require 'dodgeball/client/utils'
9
+ require 'dodgeball/client/request'
10
+ require 'dodgeball/client/response'
11
+ require 'dodgeball/client/logging'
12
+ require 'net/http'
13
+
14
+ module Dodgeball
15
+ class Client
16
+ include Dodgeball::Client::Utils
17
+ include Dodgeball::Client::Logging
18
+
19
+ # @param [Hash] opts
20
+ # @option opts [String] :write_key Your project's write_key
21
+ # @option opts [String] :dodgeball_api_url Your Dodgeball API URL
22
+ # @option opts [Proc] :on_error Handles error calls from the API.
23
+ # @option opts [Boolean] :ssl Whether to send the call via SSL
24
+ # @option opts [Boolean] :stub Whether to cause all requests to be stubbed, making it easier to test
25
+ def initialize(opts = {})
26
+ symbolize_keys!(opts)
27
+ Request.stub = opts[:stub] if opts.has_key?(:stub)
28
+
29
+ @write_key = opts[:write_key]
30
+ @dodgeball_api_url = opts[:dodgeball_api_url] || Defaults::Request::DODGEBALL_API_URL
31
+ uri = URI(opts[:dodgeball_api_url])
32
+ @host = uri.host
33
+ @port = uri.port
34
+ @on_error = opts[:on_error] || proc { |status, error| }
35
+ @ssl = opts[:ssl] || Defaults::Request::SSL
36
+
37
+ check_write_key!
38
+ end
39
+
40
+
41
+ # Verifies an event and executes a workflow
42
+ #
43
+ # @param [Hash] attrs
44
+ #
45
+ # @option attrs [Hash] :event Any input to pass to the workflow (required)
46
+ # @option attrs [String] :source_id Id of the user from the client(required)
47
+ # @option attrs [Boolean] :sync Direction to the API to return immediately or wait until the workflow returns its first resolution.
48
+ # @option attrs [String] :verification_id if there was a previous verification executed in the client (optional)
49
+ def verify(event,source_id,verification_id=nil, sync=true)
50
+ raise ArgumentError.new('No event provided') unless event
51
+ raise ArgumentError.new('No source_id provided') unless source_id
52
+ request_headers={}
53
+ request_headers[Defaults::Request::SOURCE_ID_HEADER] = source_id
54
+ request_headers[Defaults::Request::VERIFICATION_ID_HEADER] = verification_id if verification_id
55
+ body = {event: event, options: {sync: sync}}
56
+ res=execute_request('verify', body, request_headers)
57
+ res
58
+ end
59
+
60
+
61
+ private
62
+
63
+ # private: Executes a request with common code to handle results.
64
+ #
65
+ def execute_request(request_function, body, request_specific_headers)
66
+ path=generate_path(request_function)
67
+ res = Request.new(:dodgeball_api_url => @dodgeball_api_url, :ssl => @ssl).post(@write_key, path, body, request_specific_headers)
68
+ @on_error.call(res.status, res.response_body) unless res.status == 200
69
+ res
70
+ end
71
+
72
+ # private: Adds the correct path root
73
+ #
74
+ # @param [String] Function name to build into a path
75
+ def generate_path(request_function)
76
+ Defaults::Request::PATH_ROOT + request_function
77
+ end
78
+
79
+ # private: Checks that the write_key is properly initialized
80
+ def check_write_key!
81
+ raise ArgumentError, 'Write key must be initialized' if @write_key.nil?
82
+ end
83
+
84
+ include Logging
85
+ end
86
+ end
data/lib/dodgeball.rb ADDED
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dodgeball/client'
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dodgeball'
metadata ADDED
@@ -0,0 +1,179 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dodgeball-trust-sdk-ruby
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Dodgeball
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2022-02-02 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: commander
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '4.4'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '4.4'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 12.3.3
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 12.3.3
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: tzinfo
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '='
60
+ - !ruby/object:Gem::Version
61
+ version: 1.2.1
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '='
67
+ - !ruby/object:Gem::Version
68
+ version: 1.2.1
69
+ - !ruby/object:Gem::Dependency
70
+ name: activesupport
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 6.0.2
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 6.0.2
83
+ - !ruby/object:Gem::Dependency
84
+ name: pry
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 0.9.12.2
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 0.9.12.2
97
+ - !ruby/object:Gem::Dependency
98
+ name: oj
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: 3.6.2
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: 3.6.2
111
+ - !ruby/object:Gem::Dependency
112
+ name: rubocop
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: 0.78.0
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: 0.78.0
125
+ - !ruby/object:Gem::Dependency
126
+ name: codecov
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: 0.1.4
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: 0.1.4
139
+ description: This library provides functionality for Ruby-based server applications
140
+ to execute no-code workflows from the Dodgeball platform
141
+ email: hello@dodgeballhq.com
142
+ executables: []
143
+ extensions: []
144
+ extra_rdoc_files: []
145
+ files:
146
+ - lib/dodgeball.rb
147
+ - lib/dodgeball/client.rb
148
+ - lib/dodgeball/client/backoff_policy.rb
149
+ - lib/dodgeball/client/defaults.rb
150
+ - lib/dodgeball/client/logging.rb
151
+ - lib/dodgeball/client/request.rb
152
+ - lib/dodgeball/client/response.rb
153
+ - lib/dodgeball/client/utils.rb
154
+ - lib/dodgeball/client/version.rb
155
+ - lib/trust-sdk-ruby.rb
156
+ homepage: https://github.com/dodgeballhq/dodgeball-trust-sdk-ruby
157
+ licenses:
158
+ - MIT
159
+ metadata: {}
160
+ post_install_message:
161
+ rdoc_options: []
162
+ require_paths:
163
+ - lib
164
+ required_ruby_version: !ruby/object:Gem::Requirement
165
+ requirements:
166
+ - - ">="
167
+ - !ruby/object:Gem::Version
168
+ version: '2.0'
169
+ required_rubygems_version: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - ">="
172
+ - !ruby/object:Gem::Version
173
+ version: '0'
174
+ requirements: []
175
+ rubygems_version: 3.0.3.1
176
+ signing_key:
177
+ specification_version: 4
178
+ summary: The Dodgeball Ruby Server Library
179
+ test_files: []