readme-metrics 2.0.1 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +72 -0
  3. data/Gemfile +9 -9
  4. data/Gemfile.lock +28 -34
  5. data/LICENSE +1 -1
  6. data/Makefile +20 -0
  7. data/README.md +21 -102
  8. data/Rakefile +2 -2
  9. data/bin/console +3 -3
  10. data/examples/metrics-rails/.gitattributes +7 -0
  11. data/examples/metrics-rails/.gitignore +20 -0
  12. data/examples/metrics-rails/.ruby-version +1 -0
  13. data/examples/metrics-rails/Gemfile +31 -0
  14. data/examples/metrics-rails/Gemfile.lock +189 -0
  15. data/examples/metrics-rails/README.md +38 -0
  16. data/examples/metrics-rails/Rakefile +6 -0
  17. data/examples/metrics-rails/app/controllers/application_controller.rb +2 -0
  18. data/examples/metrics-rails/app/controllers/metrics_controller.rb +34 -0
  19. data/examples/metrics-rails/app/models/application_record.rb +3 -0
  20. data/examples/metrics-rails/bin/bundle +116 -0
  21. data/examples/metrics-rails/bin/rails +4 -0
  22. data/examples/metrics-rails/bin/rake +4 -0
  23. data/examples/metrics-rails/bin/setup +33 -0
  24. data/examples/metrics-rails/config/application.rb +52 -0
  25. data/examples/metrics-rails/config/boot.rb +3 -0
  26. data/examples/metrics-rails/config/credentials.yml.enc +1 -0
  27. data/examples/metrics-rails/config/database.yml +25 -0
  28. data/examples/metrics-rails/config/environment.rb +5 -0
  29. data/examples/metrics-rails/config/environments/development.rb +56 -0
  30. data/examples/metrics-rails/config/environments/production.rb +68 -0
  31. data/examples/metrics-rails/config/environments/test.rb +50 -0
  32. data/examples/metrics-rails/config/initializers/cors.rb +16 -0
  33. data/examples/metrics-rails/config/initializers/filter_parameter_logging.rb +8 -0
  34. data/examples/metrics-rails/config/initializers/inflections.rb +16 -0
  35. data/examples/metrics-rails/config/locales/en.yml +33 -0
  36. data/examples/metrics-rails/config/puma.rb +43 -0
  37. data/examples/metrics-rails/config/routes.rb +5 -0
  38. data/examples/metrics-rails/config.ru +6 -0
  39. data/examples/metrics-rails/db/seeds.rb +7 -0
  40. data/examples/metrics-rails/public/robots.txt +1 -0
  41. data/lib/readme/content_type_helper.rb +6 -6
  42. data/lib/readme/errors.rb +7 -5
  43. data/lib/readme/filter.rb +2 -2
  44. data/lib/readme/har/collection.rb +3 -1
  45. data/lib/readme/har/request_serializer.rb +14 -14
  46. data/lib/readme/har/response_serializer.rb +3 -3
  47. data/lib/readme/har/serializer.rb +12 -10
  48. data/lib/readme/http_request.rb +18 -9
  49. data/lib/readme/http_response.rb +7 -7
  50. data/lib/readme/metrics/version.rb +3 -1
  51. data/lib/readme/metrics.rb +33 -42
  52. data/lib/readme/payload.rb +14 -6
  53. data/lib/readme/request_queue.rb +4 -4
  54. data/lib/readme/webhook.rb +42 -0
  55. data/readme-metrics.gemspec +14 -15
  56. metadata +38 -33
  57. data/SECURITY.md +0 -12
@@ -3,12 +3,12 @@ module Readme
3
3
  # Assumes the includer has a `content_type` method defined.
4
4
 
5
5
  JSON_MIME_TYPES = [
6
- "application/json",
7
- "application/x-json",
8
- "text/json",
9
- "text/x-json",
10
- "+json"
11
- ]
6
+ 'application/json',
7
+ 'application/x-json',
8
+ 'text/json',
9
+ 'text/x-json',
10
+ '+json'
11
+ ].freeze
12
12
 
13
13
  def json?
14
14
  JSON_MIME_TYPES.any? { |mime_type| content_type.include?(mime_type) }
data/lib/readme/errors.rb CHANGED
@@ -1,10 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Readme
2
4
  class Errors
3
- API_KEY_ERROR = "Missing API Key"
4
- REJECT_PARAMS_ERROR = "The `reject_params` option must be an array of strings"
5
- ALLOW_ONLY_ERROR = "The `allow_only` option must be an array of strings"
6
- BUFFER_LENGTH_ERROR = "The `buffer_length` must be an Integer"
7
- DEVELOPMENT_ERROR = "The `development` option must be a boolean"
5
+ API_KEY_ERROR = 'Missing API Key'
6
+ REJECT_PARAMS_ERROR = 'The `reject_params` option must be an array of strings'
7
+ ALLOW_ONLY_ERROR = 'The `allow_only` option must be an array of strings'
8
+ BUFFER_LENGTH_ERROR = 'The `buffer_length` must be an Integer'
9
+ DEVELOPMENT_ERROR = 'The `development` option must be a boolean'
8
10
  LOGGER_ERROR = <<~MESSAGE
9
11
  The `logger` option must be class that responds to the following messages:
10
12
  :unkown, :fatal, :error, :warn, :info, :debug, :level
data/lib/readme/filter.rb CHANGED
@@ -15,7 +15,7 @@ module Readme
15
15
  def self.redact(rejected_params)
16
16
  rejected_params.each_with_object({}) do |(k, v), hash|
17
17
  # If it's a string then return the length of the redacted field
18
- hash[k.to_str] = "[REDACTED#{v.is_a?(String) ? " #{v.length}" : ""}]"
18
+ hash[k.to_str] = "[REDACTED#{v.is_a?(String) ? " #{v.length}" : ''}]"
19
19
  end
20
20
  end
21
21
 
@@ -65,7 +65,7 @@ module Readme
65
65
 
66
66
  class FilterArgsError < StandardError
67
67
  def initialize
68
- msg = "Can only supply either reject_params or allow_only, not both."
68
+ msg = 'Can only supply either reject_params or allow_only, not both.'
69
69
  super(msg)
70
70
  end
71
71
  end
@@ -1,3 +1,5 @@
1
+ require 'json'
2
+
1
3
  module Readme
2
4
  module Har
3
5
  class Collection
@@ -11,7 +13,7 @@ module Readme
11
13
  end
12
14
 
13
15
  def to_a
14
- filtered_hash.map { |name, value| {name: name, value: value} }
16
+ filtered_hash.map { |name, value| { name: name, value: value.is_a?(Hash) ? value.to_json : value } }
15
17
  end
16
18
 
17
19
  private
@@ -1,6 +1,6 @@
1
- require "cgi"
2
- require "readme/har/collection"
3
- require "readme/filter"
1
+ require 'cgi'
2
+ require 'readme/har/collection'
3
+ require 'readme/filter'
4
4
 
5
5
  module Readme
6
6
  module Har
@@ -18,7 +18,7 @@ module Readme
18
18
  httpVersion: @request.http_version,
19
19
  headers: Har::Collection.new(@filter, @request.headers).to_a,
20
20
  cookies: Har::Collection.new(@filter, @request.cookies).to_a,
21
- postData: postData,
21
+ postData: post_data,
22
22
  headersSize: -1,
23
23
  bodySize: @request.content_length
24
24
  }.compact
@@ -29,14 +29,14 @@ module Readme
29
29
  def url
30
30
  url = URI(@request.url)
31
31
  headers = @request.headers
32
- forward_proto = headers["X-Forwarded-Proto"]
33
- forward_host = headers["X-Forwarded-Host"]
32
+ forward_proto = headers['X-Forwarded-Proto']
33
+ forward_host = headers['X-Forwarded-Host']
34
34
  url.host = forward_host if forward_host.is_a?(String)
35
35
  url.scheme = forward_proto if forward_proto.is_a?(String)
36
36
  url.to_s
37
37
  end
38
38
 
39
- def postData
39
+ def post_data
40
40
  if @request.content_type.nil?
41
41
  nil
42
42
  elsif @request.form_data?
@@ -59,22 +59,22 @@ module Readme
59
59
  def request_body
60
60
  if @filter.pass_through?
61
61
  pass_through_body
62
- elsif is_form_urlencoded?
62
+ elsif form_urlencoded?
63
63
  form_urlencoded_body
64
- elsif is_json?
64
+ elsif json?
65
65
  json_body
66
66
  else
67
67
  @request.body
68
68
  end
69
69
  end
70
70
 
71
- def is_json?
72
- ["application/json", "application/x-json", "text/json", "text/x-json"]
73
- .include?(@request.content_type) || @request.content_type.include?("+json")
71
+ def json?
72
+ ['application/json', 'application/x-json', 'text/json', 'text/x-json']
73
+ .include?(@request.content_type) || @request.content_type.include?('+json')
74
74
  end
75
75
 
76
- def is_form_urlencoded?
77
- @request.content_type == "application/x-www-form-urlencoded"
76
+ def form_urlencoded?
77
+ @request.content_type == 'application/x-www-form-urlencoded'
78
78
  end
79
79
 
80
80
  def json_body
@@ -1,5 +1,5 @@
1
- require "rack/utils"
2
- require "readme/har/collection"
1
+ require 'rack/utils'
2
+ require 'readme/har/collection'
3
3
 
4
4
  module Readme
5
5
  module Har
@@ -37,7 +37,7 @@ module Readme
37
37
  end
38
38
 
39
39
  def empty_content
40
- {mimeType: "", size: 0}
40
+ { mimeType: '', size: 0 }
41
41
  end
42
42
 
43
43
  def json_content
@@ -1,13 +1,15 @@
1
- require "rack"
2
- require "readme/metrics"
3
- require "readme/har/request_serializer"
4
- require "readme/har/response_serializer"
5
- require "readme/har/collection"
1
+ # frozen_string_literal: true
2
+
3
+ require 'rack'
4
+ require 'readme/metrics'
5
+ require 'readme/har/request_serializer'
6
+ require 'readme/har/response_serializer'
7
+ require 'readme/har/collection'
6
8
 
7
9
  module Readme
8
10
  module Har
9
11
  class Serializer
10
- HAR_VERSION = "1.2"
12
+ HAR_VERSION = '1.2'
11
13
 
12
14
  def initialize(request, response, start_time, end_time, filter)
13
15
  @http_request = request
@@ -17,7 +19,7 @@ module Readme
17
19
  @filter = filter
18
20
  end
19
21
 
20
- def to_json
22
+ def to_json(*_args)
21
23
  {
22
24
  log: {
23
25
  version: HAR_VERSION,
@@ -31,9 +33,9 @@ module Readme
31
33
 
32
34
  def creator
33
35
  {
34
- name: Readme::Metrics::SDK_NAME,
36
+ name: 'readme-metrics (ruby)',
35
37
  version: Readme::Metrics::VERSION,
36
- comment: "#{Readme::Metrics::PLATFORM}/#{RUBY_VERSION}"
38
+ comment: "#{RUBY_PLATFORM}/#{RUBY_VERSION}" # arm64-darwin21/2.7.2
37
39
  }
38
40
  end
39
41
 
@@ -44,7 +46,7 @@ module Readme
44
46
  timings: timings,
45
47
  request: request,
46
48
  response: response,
47
- startedDateTime: @start_time.iso8601,
49
+ startedDateTime: @start_time.utc.iso8601(3),
48
50
  time: elapsed_time
49
51
  }
50
52
  ]
@@ -1,6 +1,6 @@
1
- require "rack"
2
- require "rack/request"
3
- require_relative "content_type_helper"
1
+ require 'rack'
2
+ require 'rack/request'
3
+ require_relative 'content_type_helper'
4
4
 
5
5
  module Readme
6
6
  class HttpRequest
@@ -11,7 +11,7 @@ module Readme
11
11
  Rack::HTTP_VERSION,
12
12
  Rack::HTTP_HOST,
13
13
  Rack::HTTP_PORT
14
- ]
14
+ ].freeze
15
15
 
16
16
  def initialize(env)
17
17
  @request = Rack::Request.new(env)
@@ -50,7 +50,7 @@ module Readme
50
50
  end
51
51
 
52
52
  def options?
53
- @request.request_method == "OPTIONS"
53
+ @request.request_method == 'OPTIONS'
54
54
  end
55
55
 
56
56
  def headers
@@ -60,6 +60,7 @@ module Readme
60
60
  .to_h
61
61
  .transform_keys { |header| normalize_header_name(header) }
62
62
  .merge unprefixed_headers
63
+ .merge host_header
63
64
  end
64
65
 
65
66
  def body
@@ -82,20 +83,28 @@ module Readme
82
83
  # Other "headers" like version and host are prefixed with `HTTP_` by Rack but
83
84
  # don't seem to be considered legit HTTP headers.
84
85
  def http_header?(name)
85
- name.start_with?("HTTP") && !HTTP_NON_HEADERS.include?(name)
86
+ name.start_with?('HTTP') && !HTTP_NON_HEADERS.include?(name)
86
87
  end
87
88
 
88
89
  # Headers like `Content-Type: application/json` come into rack like
89
90
  # `"HTTP_CONTENT_TYPE" => "application/json"`.
90
91
  def normalize_header_name(header)
91
- header.delete_prefix("HTTP_").split("_").map(&:capitalize).join("-")
92
+ header.delete_prefix('HTTP_').split('_').map(&:capitalize).join('-')
92
93
  end
93
94
 
94
95
  # These special headers are explicitly _not_ prefixed with HTTP_ in the Rack
95
96
  # env so we need to add them in manually
96
97
  def unprefixed_headers
97
- {"Content-Type" => @request.content_type,
98
- "Content-Length" => @request.content_length}.compact
98
+ {
99
+ 'Content-Type' => @request.content_type,
100
+ 'Content-Length' => @request.content_length
101
+ }.compact
102
+ end
103
+
104
+ def host_header
105
+ {
106
+ 'Host' => @request.host
107
+ }.compact
99
108
  end
100
109
  end
101
110
  end
@@ -1,6 +1,6 @@
1
- require "rack"
2
- require "rack/response"
3
- require_relative "content_type_helper"
1
+ require 'rack'
2
+ require 'rack/response'
3
+ require_relative 'content_type_helper'
4
4
 
5
5
  module Readme
6
6
  class HttpResponse < SimpleDelegator
@@ -13,22 +13,22 @@ module Readme
13
13
  def body
14
14
  if raw_body.respond_to?(:rewind)
15
15
  raw_body.rewind
16
- content = raw_body.each.reduce("", :+)
16
+ content = raw_body.each.sum('')
17
17
  raw_body.rewind
18
18
 
19
19
  content
20
20
  else
21
- raw_body.each.reduce("", :+)
21
+ raw_body.each.sum('')
22
22
  end
23
23
  end
24
24
 
25
25
  def content_length
26
26
  if empty_body_status?
27
27
  0
28
- elsif !headers["Content-Length"]
28
+ elsif !headers['Content-Length']
29
29
  body.bytesize
30
30
  else
31
- headers["Content-Length"].to_i
31
+ headers['Content-Length'].to_i
32
32
  end
33
33
  end
34
34
 
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Readme
2
4
  class Metrics
3
- VERSION = "2.0.1"
5
+ VERSION = '2.2.0'
4
6
  end
5
7
  end
@@ -1,33 +1,21 @@
1
- require "readme/metrics/version"
2
- require "readme/har/serializer"
3
- require "readme/filter"
4
- require "readme/payload"
5
- require "readme/request_queue"
6
- require "readme/errors"
7
- require "readme/http_request"
8
- require "readme/http_response"
9
- require "httparty"
10
- require "logger"
11
- require "os"
1
+ # frozen_string_literal: true
2
+
3
+ require 'readme/metrics/version'
4
+ require 'readme/har/serializer'
5
+ require 'readme/filter'
6
+ require 'readme/payload'
7
+ require 'readme/request_queue'
8
+ require 'readme/errors'
9
+ require 'readme/http_request'
10
+ require 'readme/http_response'
11
+ require 'httparty'
12
+ require 'logger'
12
13
 
13
14
  module Readme
14
15
  class Metrics
15
- def self.platform
16
- if OS.windows?
17
- "windows"
18
- elsif OS.mac?
19
- "mac"
20
- elsif OS.linux?
21
- "linux"
22
- else
23
- "unknown"
24
- end
25
- end
26
-
27
- SDK_NAME = "Readme.io Ruby SDK"
28
- PLATFORM = platform
16
+ SDK_NAME = 'readme-metrics'
29
17
  DEFAULT_BUFFER_LENGTH = 1
30
- ENDPOINT = "https://metrics.readme.io/v1/request"
18
+ ENDPOINT = URI.join(ENV['README_METRICS_SERVER'] || 'https://metrics.readme.io', '/v1/request')
31
19
 
32
20
  def self.logger
33
21
  @@logger
@@ -77,15 +65,16 @@ module Readme
77
65
  request = HttpRequest.new(env)
78
66
  har = Har::Serializer.new(request, response, start_time, end_time, @filter)
79
67
  user_info = @get_user_info.call(env)
68
+ ip = env['REMOTE_ADDR']
80
69
 
81
70
  if !user_info_valid?(user_info)
82
71
  Readme::Metrics.logger.warn Errors.bad_block_message(user_info)
83
72
  elsif request.options?
84
- Readme::Metrics.logger.info "OPTIONS request omitted from ReadMe API logging"
73
+ Readme::Metrics.logger.info 'OPTIONS request omitted from ReadMe API logging'
85
74
  elsif !can_filter? request, response
86
75
  Readme::Metrics.logger.warn "Request or response body MIME type isn't supported for filtering. Omitting request from ReadMe API logging"
87
76
  else
88
- payload = Payload.new(har, user_info, development: @development)
77
+ payload = Payload.new(har, user_info, ip, development: @development)
89
78
  @@request_queue.push(payload.to_json) unless payload.ignore
90
79
  end
91
80
  end
@@ -121,34 +110,36 @@ module Readme
121
110
  raise Errors::ConfigurationError, Errors::BUFFER_LENGTH_ERROR
122
111
  end
123
112
 
124
- if options[:development] && !is_a_boolean?(options[:development])
113
+ if options[:development] && !a_boolean?(options[:development])
125
114
  raise Errors::ConfigurationError, Errors::DEVELOPMENT_ERROR
126
115
  end
127
116
 
128
- if options[:logger] && has_logger_inferface?(options[:logger])
117
+ if options[:logger] && logger_inferface?(options[:logger])
129
118
  raise Errors::ConfigurationError, Errors::LOGGER_ERROR
130
119
  end
131
120
  end
132
121
 
133
- def has_logger_inferface?(logger)
134
- [
135
- :unknown,
136
- :fatal,
137
- :error,
138
- :warn,
139
- :info,
140
- :debug
122
+ def logger_inferface?(logger)
123
+ %i[
124
+ unknown
125
+ fatal
126
+ error
127
+ warn
128
+ info
129
+ debug
141
130
  ].any? { |message| !logger.respond_to? message }
142
131
  end
143
132
 
144
- def is_a_boolean?(arg)
145
- arg == true || arg == false
133
+ def a_boolean?(arg)
134
+ [true, false].include?(arg)
146
135
  end
147
136
 
137
+ # rubocop:disable Style/InverseMethods
148
138
  def user_info_valid?(user_info)
149
- !user_info.nil? &&
139
+ (!user_info.nil? &&
150
140
  !user_info.values.any?(&:nil?) &&
151
- user_info.has_key?(:api_key) || user_info.has_key?(:id)
141
+ user_info.key?(:api_key)) || user_info.key?(:id)
152
142
  end
143
+ # rubocop:enable Style/InverseMethods
153
144
  end
154
145
  end
@@ -1,24 +1,32 @@
1
- require "uuid"
1
+ require 'socket'
2
+ require 'securerandom'
3
+
4
+ def validate_uuid(uuid)
5
+ return if uuid.nil?
6
+
7
+ uuid.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-5][0-9a-f]{3}-[089ab][0-9a-f]{3}-[0-9a-f]{12}$/i)
8
+ end
2
9
 
3
10
  module Readme
4
11
  class Payload
5
12
  attr_reader :ignore
6
13
 
7
- def initialize(har, info, development:)
14
+ def initialize(har, info, ip_address, development:)
8
15
  @har = har
9
16
  @user_info = info.slice(:id, :label, :email)
10
17
  @user_info[:id] = info[:api_key] unless info[:api_key].nil? # swap api_key for id if api_key is present
11
18
  @log_id = info[:log_id]
12
19
  @ignore = info[:ignore]
20
+ @ip_address = ip_address
13
21
  @development = development
14
- @uuid = UUID.new
22
+ @uuid = SecureRandom.uuid
15
23
  end
16
24
 
17
- def to_json
25
+ def to_json(*_args)
18
26
  {
19
- logId: UUID.validate(@log_id) ? @log_id : @uuid.generate,
27
+ _id: validate_uuid(@log_id) ? @log_id : @uuid,
20
28
  group: @user_info,
21
- clientIPAddress: "1.1.1.1",
29
+ clientIPAddress: @ip_address,
22
30
  development: @development,
23
31
  request: JSON.parse(@har.to_json)
24
32
  }.to_json
@@ -1,4 +1,4 @@
1
- require "readme/metrics"
1
+ require 'readme/metrics'
2
2
 
3
3
  module Readme
4
4
  class RequestQueue
@@ -30,8 +30,8 @@ module Readme
30
30
  Thread.new do
31
31
  HTTParty.post(
32
32
  Readme::Metrics::ENDPOINT,
33
- basic_auth: {username: @api_key, password: ""},
34
- headers: {"Content-Type" => "application/json"},
33
+ basic_auth: { username: @api_key, password: '' },
34
+ headers: { 'Content-Type' => 'application/json' },
35
35
  body: to_json(payloads)
36
36
  )
37
37
  end
@@ -42,7 +42,7 @@ module Readme
42
42
  end
43
43
 
44
44
  def to_json(payloads)
45
- "[#{payloads.join(", ")}]"
45
+ "[#{payloads.join(', ')}]"
46
46
  end
47
47
  end
48
48
  end
@@ -0,0 +1,42 @@
1
+ require 'readme/metrics'
2
+
3
+ module Readme
4
+ class MissingSignatureError < ArgumentError
5
+ def message
6
+ 'Missing Signature'
7
+ end
8
+ end
9
+
10
+ class ExpiredSignatureError < RuntimeError
11
+ def message
12
+ 'Expired Signature'
13
+ end
14
+ end
15
+
16
+ class InvalidSignatureError < RuntimeError
17
+ def message
18
+ 'Invalid Signature'
19
+ end
20
+ end
21
+
22
+ class Webhook
23
+ def self.verify(body, signature, secret)
24
+ raise MissingSignatureError unless signature
25
+
26
+ parsed = signature.split(',').each_with_object({ time: -1, readme_signature: '' }) do |item, accum|
27
+ k, v = item.split('=')
28
+ accum[:time] = v if k.eql? 't'
29
+ accum[:readme_signature] = v if k.eql? 'v0'
30
+ end
31
+
32
+ # Make sure timestamp is recent to prevent replay attacks
33
+ thirty_minutes = 30 * 60
34
+ raise ExpiredSignatureError if Time.now.utc - Time.at(0, parsed[:time].to_i, :millisecond).utc > thirty_minutes
35
+
36
+ # Verify the signature is valid
37
+ unsigned = "#{parsed[:time]}.#{body}"
38
+ mac = OpenSSL::HMAC.hexdigest('SHA256', secret, unsigned)
39
+ raise InvalidSignatureError if mac != parsed[:readme_signature]
40
+ end
41
+ end
42
+ end
@@ -1,31 +1,30 @@
1
- require_relative "lib/readme/metrics/version"
1
+ require_relative 'lib/readme/metrics/version'
2
2
 
3
3
  Gem::Specification.new do |spec|
4
- spec.name = "readme-metrics"
4
+ spec.name = 'readme-metrics'
5
5
  spec.version = Readme::Metrics::VERSION
6
- spec.authors = ["ReadMe"]
7
- spec.email = ["support@readme.io"]
8
- spec.license = "ISC"
6
+ spec.authors = ['ReadMe']
7
+ spec.email = ['support@readme.io']
8
+ spec.license = 'ISC'
9
9
 
10
10
  spec.summary = "SDK for Readme's metrics API"
11
11
  spec.description = "Middleware for logging requests to Readme's metrics API"
12
- spec.homepage = "https://docs.readme.com/metrics/docs/getting-started-with-api-metrics"
13
- spec.required_ruby_version = Gem::Requirement.new(">= 2.7.0")
12
+ spec.homepage = 'https://docs.readme.com/metrics/docs/getting-started-with-api-metrics'
13
+ spec.required_ruby_version = Gem::Requirement.new('>= 2.7.0')
14
14
 
15
- spec.metadata["homepage_uri"] = spec.homepage
16
- spec.metadata["source_code_uri"] = "https://github.com/readmeio/metrics-sdks/blob/main/packages/ruby"
17
- spec.metadata["changelog_uri"] = "https://github.com/readmeio/metrics-sdks/blob/main/packages/ruby/CHANGELOG.md"
15
+ spec.metadata['homepage_uri'] = spec.homepage
16
+ spec.metadata['source_code_uri'] = 'https://github.com/readmeio/metrics-sdks/tree/main/packages/ruby'
17
+ spec.metadata['changelog_uri'] = 'https://github.com/readmeio/metrics-sdks/blob/main/CHANGELOG.md'
18
18
 
19
19
  # Specify which files should be added to the gem when it is released.
20
20
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
21
- spec.files = Dir.chdir(File.expand_path("..", __FILE__)) do
21
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
22
22
  `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
23
23
  end
24
+
24
25
  # spec.bindir = "exe"
25
26
  # spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
26
- spec.require_paths = ["lib"]
27
+ spec.require_paths = ['lib']
27
28
 
28
- spec.add_runtime_dependency "httparty", "~> 0.18"
29
- spec.add_runtime_dependency "uuid", "~> 2.3.8"
30
- spec.add_runtime_dependency "os", "~> 1.1.4"
29
+ spec.add_runtime_dependency 'httparty', '~> 0.18'
31
30
  end