kit-rb 0.2.0 → 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/.githooks/pre-commit +21 -0
- data/.githooks/pre-push +6 -0
- data/CHANGELOG.md +70 -0
- data/README.md +58 -16
- data/docs/DESIGN.md +38 -14
- data/docs/TASKS.md +47 -0
- data/lib/kit/auth/api_key.rb +7 -0
- data/lib/kit/auth/credential.rb +22 -0
- data/lib/kit/auth/oauth.rb +7 -8
- data/lib/kit/connection.rb +53 -20
- data/lib/kit/errors.rb +52 -15
- data/lib/kit/oauth/token.rb +9 -0
- data/lib/kit/objects/account.rb +44 -3
- data/lib/kit/objects/bulk_result.rb +39 -0
- data/lib/kit/objects/custom_field.rb +4 -2
- data/lib/kit/objects/post.rb +4 -2
- data/lib/kit/objects/sequence_email.rb +5 -2
- data/lib/kit/objects/subscriber.rb +16 -9
- data/lib/kit/objects/tag.rb +5 -3
- data/lib/kit/objects/webhook_endpoint.rb +7 -2
- data/lib/kit/pagination.rb +47 -3
- data/lib/kit/resources/account.rb +4 -4
- data/lib/kit/resources/base.rb +46 -4
- data/lib/kit/resources/broadcasts.rb +32 -13
- data/lib/kit/resources/bulk.rb +18 -20
- data/lib/kit/resources/custom_fields.rb +2 -2
- data/lib/kit/resources/forms.rb +3 -3
- data/lib/kit/resources/posts.rb +1 -1
- data/lib/kit/resources/purchases.rb +10 -6
- data/lib/kit/resources/sequences.rb +49 -25
- data/lib/kit/resources/snippets.rb +13 -8
- data/lib/kit/resources/subscribers.rb +23 -9
- data/lib/kit/resources/tags.rb +6 -6
- data/lib/kit/resources/webhook_endpoints.rb +12 -4
- data/lib/kit/resources/webhooks.rb +1 -1
- data/lib/kit/version.rb +1 -1
- data/lib/kit/webhooks/delivery.rb +61 -0
- data/lib/kit/webhooks/events.rb +113 -0
- data/lib/kit/webhooks/signature.rb +100 -0
- data/lib/kit-rb.rb +5 -0
- data/sig/kit-rb.rbs +170 -29
- metadata +11 -1
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Kit
|
|
4
|
+
module Webhooks
|
|
5
|
+
# Event types a webhook endpoint (/v4/webhook_endpoints) can subscribe to,
|
|
6
|
+
# as listed at developers.kit.com/webhooks/event-types (verified
|
|
7
|
+
# 2026-09-06). Use these instead of hand-typed strings:
|
|
8
|
+
#
|
|
9
|
+
# client.webhook_endpoints.create(url: url, events: [Events::SUBSCRIBER_CREATED, Events::TAG_CREATED])
|
|
10
|
+
#
|
|
11
|
+
# DATA_KEYS names the keys each event's `data` carries. PLANNED events are
|
|
12
|
+
# documented but not yet delivered.
|
|
13
|
+
module Events
|
|
14
|
+
SUBSCRIBER_CREATED = "subscriber.created"
|
|
15
|
+
SUBSCRIBER_ACTIVATED = "subscriber.activated"
|
|
16
|
+
SUBSCRIBER_UNSUBSCRIBED = "subscriber.unsubscribed"
|
|
17
|
+
SUBSCRIBER_BOUNCED = "subscriber.bounced"
|
|
18
|
+
SUBSCRIBER_COMPLAINED = "subscriber.complained"
|
|
19
|
+
SUBSCRIBER_SUBSCRIBED_TO_FORM = "subscriber.subscribed_to_form"
|
|
20
|
+
SUBSCRIBER_ADDED_TO_SEQUENCE = "subscriber.added_to_sequence"
|
|
21
|
+
SUBSCRIBER_SEQUENCE_COMPLETED = "subscriber.sequence_completed"
|
|
22
|
+
SUBSCRIBER_TAG_ADDED = "subscriber.tag_added"
|
|
23
|
+
SUBSCRIBER_TAG_REMOVED = "subscriber.tag_removed"
|
|
24
|
+
SUBSCRIBER_CUSTOM_FIELD_VALUE_UPDATED = "subscriber.custom_field_value_updated"
|
|
25
|
+
SUBSCRIBER_PRODUCT_PURCHASED = "subscriber.product_purchased" # planned
|
|
26
|
+
SUBSCRIBER_LINK_CLICKED = "subscriber.link_clicked" # planned
|
|
27
|
+
SUBSCRIBER_EMAIL_OPENED = "subscriber.email_opened" # planned
|
|
28
|
+
TAG_CREATED = "tag.created"
|
|
29
|
+
TAG_DELETED = "tag.deleted"
|
|
30
|
+
CUSTOM_FIELD_CREATED = "custom_field.created"
|
|
31
|
+
CUSTOM_FIELD_DELETED = "custom_field.deleted"
|
|
32
|
+
SEQUENCE_CREATED = "sequence.created"
|
|
33
|
+
SEQUENCE_DELETED = "sequence.deleted"
|
|
34
|
+
SEQUENCE_PUBLISHED = "sequence.published"
|
|
35
|
+
SEQUENCE_DISABLED = "sequence.disabled"
|
|
36
|
+
BROADCAST_CREATED = "broadcast.created"
|
|
37
|
+
BROADCAST_SENT = "broadcast.sent"
|
|
38
|
+
BROADCAST_DELETED = "broadcast.deleted"
|
|
39
|
+
POST_PUBLISHED = "post.published"
|
|
40
|
+
LANDING_PAGE_CREATED = "landing_page.created" # planned
|
|
41
|
+
LANDING_PAGE_DELETED = "landing_page.deleted" # planned
|
|
42
|
+
|
|
43
|
+
DATA_KEYS = {
|
|
44
|
+
SUBSCRIBER_CREATED => %w[subscriber], SUBSCRIBER_ACTIVATED => %w[subscriber],
|
|
45
|
+
SUBSCRIBER_UNSUBSCRIBED => %w[subscriber], SUBSCRIBER_BOUNCED => %w[subscriber],
|
|
46
|
+
SUBSCRIBER_COMPLAINED => %w[subscriber], SUBSCRIBER_SUBSCRIBED_TO_FORM => %w[subscriber form],
|
|
47
|
+
SUBSCRIBER_ADDED_TO_SEQUENCE => %w[subscriber sequence],
|
|
48
|
+
SUBSCRIBER_SEQUENCE_COMPLETED => %w[subscriber sequence],
|
|
49
|
+
SUBSCRIBER_TAG_ADDED => %w[subscriber tag], SUBSCRIBER_TAG_REMOVED => %w[subscriber tag],
|
|
50
|
+
SUBSCRIBER_CUSTOM_FIELD_VALUE_UPDATED => %w[subscriber custom_field],
|
|
51
|
+
SUBSCRIBER_PRODUCT_PURCHASED => %w[subscriber], SUBSCRIBER_LINK_CLICKED => %w[subscriber],
|
|
52
|
+
SUBSCRIBER_EMAIL_OPENED => %w[subscriber],
|
|
53
|
+
TAG_CREATED => %w[tag], TAG_DELETED => %w[tag],
|
|
54
|
+
CUSTOM_FIELD_CREATED => %w[custom_field], CUSTOM_FIELD_DELETED => %w[custom_field],
|
|
55
|
+
SEQUENCE_CREATED => %w[sequence], SEQUENCE_DELETED => %w[sequence],
|
|
56
|
+
SEQUENCE_PUBLISHED => %w[sequence], SEQUENCE_DISABLED => %w[sequence],
|
|
57
|
+
BROADCAST_CREATED => %w[broadcast], BROADCAST_SENT => %w[broadcast], BROADCAST_DELETED => %w[broadcast],
|
|
58
|
+
POST_PUBLISHED => %w[post],
|
|
59
|
+
LANDING_PAGE_CREATED => [], LANDING_PAGE_DELETED => []
|
|
60
|
+
}.freeze
|
|
61
|
+
|
|
62
|
+
PLANNED = [
|
|
63
|
+
SUBSCRIBER_PRODUCT_PURCHASED, SUBSCRIBER_LINK_CLICKED, SUBSCRIBER_EMAIL_OPENED,
|
|
64
|
+
LANDING_PAGE_CREATED, LANDING_PAGE_DELETED
|
|
65
|
+
].freeze
|
|
66
|
+
|
|
67
|
+
ALL = DATA_KEYS.keys.freeze
|
|
68
|
+
AVAILABLE = (ALL - PLANNED).freeze
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Event names for the previous-generation /v4/webhooks (one event per
|
|
72
|
+
# webhook, unsigned). Kept for existing integrations; new ones should use
|
|
73
|
+
# webhook endpoints. REQUIRED_PARAM names the extra key the `event` hash
|
|
74
|
+
# must carry for the events that are scoped to a resource:
|
|
75
|
+
#
|
|
76
|
+
# client.webhooks.create(target_url: url,
|
|
77
|
+
# event: { name: LegacyEvents::TAG_ADD, tag_id: 12 })
|
|
78
|
+
module LegacyEvents
|
|
79
|
+
SUBSCRIBER_ACTIVATE = "subscriber.subscriber_activate"
|
|
80
|
+
SUBSCRIBER_UNSUBSCRIBE = "subscriber.subscriber_unsubscribe"
|
|
81
|
+
SUBSCRIBER_BOUNCE = "subscriber.subscriber_bounce"
|
|
82
|
+
SUBSCRIBER_COMPLAIN = "subscriber.subscriber_complain"
|
|
83
|
+
FORM_SUBSCRIBE = "subscriber.form_subscribe"
|
|
84
|
+
COURSE_SUBSCRIBE = "subscriber.course_subscribe"
|
|
85
|
+
COURSE_COMPLETE = "subscriber.course_complete"
|
|
86
|
+
LINK_CLICK = "subscriber.link_click"
|
|
87
|
+
PRODUCT_PURCHASE = "subscriber.product_purchase"
|
|
88
|
+
TAG_ADD = "subscriber.tag_add"
|
|
89
|
+
TAG_REMOVE = "subscriber.tag_remove"
|
|
90
|
+
PURCHASE_CREATE = "purchase.purchase_create"
|
|
91
|
+
FIELD_CREATED = "custom_field.field_created"
|
|
92
|
+
FIELD_DELETED = "custom_field.field_deleted"
|
|
93
|
+
FIELD_VALUE_UPDATED = "custom_field.field_value_updated"
|
|
94
|
+
|
|
95
|
+
REQUIRED_PARAM = {
|
|
96
|
+
FORM_SUBSCRIBE => :form_id,
|
|
97
|
+
COURSE_SUBSCRIBE => :sequence_id,
|
|
98
|
+
COURSE_COMPLETE => :sequence_id,
|
|
99
|
+
LINK_CLICK => :initiator_value,
|
|
100
|
+
PRODUCT_PURCHASE => :product_id,
|
|
101
|
+
TAG_ADD => :tag_id,
|
|
102
|
+
TAG_REMOVE => :tag_id,
|
|
103
|
+
FIELD_VALUE_UPDATED => :custom_field_id
|
|
104
|
+
}.freeze
|
|
105
|
+
|
|
106
|
+
ALL = [
|
|
107
|
+
SUBSCRIBER_ACTIVATE, SUBSCRIBER_UNSUBSCRIBE, SUBSCRIBER_BOUNCE, SUBSCRIBER_COMPLAIN,
|
|
108
|
+
FORM_SUBSCRIBE, COURSE_SUBSCRIBE, COURSE_COMPLETE, LINK_CLICK, PRODUCT_PURCHASE,
|
|
109
|
+
TAG_ADD, TAG_REMOVE, PURCHASE_CREATE, FIELD_CREATED, FIELD_DELETED, FIELD_VALUE_UPDATED
|
|
110
|
+
].freeze
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "openssl"
|
|
4
|
+
|
|
5
|
+
module Kit
|
|
6
|
+
module Webhooks
|
|
7
|
+
# Raised when a delivery's X-Kit-Signature is missing, malformed, stale, or
|
|
8
|
+
# does not match the body under any supplied secret.
|
|
9
|
+
class SignatureError < Error; end
|
|
10
|
+
|
|
11
|
+
# Verifies the `X-Kit-Signature` header Kit sends with every webhook-endpoint
|
|
12
|
+
# delivery (verified against developers.kit.com/webhooks/verifying-signatures,
|
|
13
|
+
# 2026-09-06):
|
|
14
|
+
#
|
|
15
|
+
# X-Kit-Signature: t=1753797130,v1=<hex>[,v1=<hex>]
|
|
16
|
+
#
|
|
17
|
+
# Each v1 is hex(HMAC-SHA256(secret, "#{t}.#{raw_body}")). During a secret
|
|
18
|
+
# rotation the header carries two v1 entries (new and previous secret), so
|
|
19
|
+
# a delivery is valid when any v1 matches any of the secrets you hold.
|
|
20
|
+
#
|
|
21
|
+
# Kit::Webhooks::Signature.verify!(request.raw_post, request.headers["X-Kit-Signature"],
|
|
22
|
+
# secret: ENV["KIT_WEBHOOK_SECRET"])
|
|
23
|
+
module Signature
|
|
24
|
+
HEADER = "X-Kit-Signature"
|
|
25
|
+
SCHEME = "v1"
|
|
26
|
+
DEFAULT_TOLERANCE = 300 # seconds; Kit's recommended replay window
|
|
27
|
+
|
|
28
|
+
Parsed = Data.define(:timestamp, :signatures)
|
|
29
|
+
|
|
30
|
+
# hex(HMAC-SHA256(secret, "t.payload")) — what Kit puts in v1.
|
|
31
|
+
def self.compute(secret, timestamp, payload)
|
|
32
|
+
OpenSSL::HMAC.hexdigest("SHA256", secret, "#{timestamp}.#{payload}")
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Splits the header into its timestamp and v1 signatures. Pairs are kept
|
|
36
|
+
# as a list, not a Hash: a rotation sends two v1 entries.
|
|
37
|
+
def self.parse(header)
|
|
38
|
+
raise SignatureError, "missing #{HEADER} header" if header.nil? || header.strip.empty?
|
|
39
|
+
|
|
40
|
+
pairs = header.split(",").map { |pair| pair.strip.split("=", 2) }
|
|
41
|
+
signatures = pairs.filter_map { |key, value| value if key == SCHEME }
|
|
42
|
+
raise SignatureError, "malformed #{HEADER} header: #{header.inspect}" if signatures.empty?
|
|
43
|
+
|
|
44
|
+
Parsed.new(timestamp: parse_timestamp(pairs, header), signatures: signatures)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def self.parse_timestamp(pairs, header)
|
|
48
|
+
value = pairs.find { |key, _| key == "t" }&.last
|
|
49
|
+
raise SignatureError, "malformed #{HEADER} header: #{header.inspect}" if value.nil?
|
|
50
|
+
raise SignatureError, "malformed timestamp in #{HEADER}: #{value.inspect}" unless value.match?(/\A\d+\z/)
|
|
51
|
+
|
|
52
|
+
value.to_i
|
|
53
|
+
end
|
|
54
|
+
private_class_method :parse_timestamp
|
|
55
|
+
|
|
56
|
+
# True when the payload was signed by one of `secret` (a String, or an
|
|
57
|
+
# Array of Strings while you hold both sides of a rotation) within
|
|
58
|
+
# `tolerance` seconds of `now`. Constant-time comparison.
|
|
59
|
+
def self.verify?(payload, header, secret:, tolerance: DEFAULT_TOLERANCE, now: Time.now.to_i)
|
|
60
|
+
verify!(payload, header, secret: secret, tolerance: tolerance, now: now)
|
|
61
|
+
true
|
|
62
|
+
rescue SignatureError
|
|
63
|
+
false
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# As #verify?, but raises SignatureError describing why the delivery was
|
|
67
|
+
# rejected. `payload` must be the raw request body, byte for byte.
|
|
68
|
+
def self.verify!(payload, header, secret:, tolerance: DEFAULT_TOLERANCE, now: Time.now.to_i)
|
|
69
|
+
secrets = secrets_from(secret)
|
|
70
|
+
parsed = parse(header)
|
|
71
|
+
check_freshness(parsed.timestamp, tolerance, now)
|
|
72
|
+
return true if secrets.any? { |value| matches?(value, parsed, payload) }
|
|
73
|
+
|
|
74
|
+
raise SignatureError, "no #{SCHEME} signature matched the payload"
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def self.secrets_from(secret)
|
|
78
|
+
secrets = Array(secret).reject { |value| value.nil? || value.empty? }
|
|
79
|
+
raise ArgumentError, "at least one webhook secret is required" if secrets.empty?
|
|
80
|
+
|
|
81
|
+
secrets
|
|
82
|
+
end
|
|
83
|
+
private_class_method :secrets_from
|
|
84
|
+
|
|
85
|
+
def self.check_freshness(timestamp, tolerance, now)
|
|
86
|
+
return unless tolerance
|
|
87
|
+
|
|
88
|
+
age = (now - timestamp).abs
|
|
89
|
+
raise SignatureError, "timestamp outside tolerance (#{age}s > #{tolerance}s)" if age > tolerance
|
|
90
|
+
end
|
|
91
|
+
private_class_method :check_freshness
|
|
92
|
+
|
|
93
|
+
def self.matches?(secret, parsed, payload)
|
|
94
|
+
expected = compute(secret, parsed.timestamp, payload)
|
|
95
|
+
parsed.signatures.any? { |given| OpenSSL.secure_compare(expected, given) }
|
|
96
|
+
end
|
|
97
|
+
private_class_method :matches?
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
data/lib/kit-rb.rb
CHANGED
|
@@ -15,6 +15,7 @@ end
|
|
|
15
15
|
require "kit/version"
|
|
16
16
|
require "kit/errors"
|
|
17
17
|
require "kit/configuration"
|
|
18
|
+
require "kit/auth/credential"
|
|
18
19
|
require "kit/auth/api_key"
|
|
19
20
|
require "kit/auth/oauth"
|
|
20
21
|
require "kit/connection"
|
|
@@ -43,6 +44,10 @@ require "kit/objects/segment"
|
|
|
43
44
|
require "kit/objects/post"
|
|
44
45
|
require "kit/objects/snippet"
|
|
45
46
|
require "kit/objects/purchase"
|
|
47
|
+
require "kit/objects/bulk_result"
|
|
48
|
+
require "kit/webhooks/signature"
|
|
49
|
+
require "kit/webhooks/delivery"
|
|
50
|
+
require "kit/webhooks/events"
|
|
46
51
|
require "kit/resources/base"
|
|
47
52
|
require "kit/resources/account"
|
|
48
53
|
require "kit/resources/subscribers"
|
data/sig/kit-rb.rbs
CHANGED
|
@@ -1,22 +1,35 @@
|
|
|
1
|
-
# RBS type signatures for the public surface of kit-rb
|
|
2
|
-
#
|
|
1
|
+
# RBS type signatures for the public surface of kit-rb, checked by Steep
|
|
2
|
+
# (`bundle exec rake steep`). Keep in step with lib/.
|
|
3
3
|
|
|
4
4
|
module Kit
|
|
5
5
|
VERSION: String
|
|
6
6
|
DEFAULT_BASE_URL: String
|
|
7
7
|
|
|
8
8
|
class Error < StandardError
|
|
9
|
+
STATUS_CLASSES: Hash[Integer, singleton(APIError)]
|
|
9
10
|
def self.class_for: (Integer status) -> singleton(APIError)
|
|
10
11
|
end
|
|
11
12
|
class ConfigurationError < Error
|
|
12
13
|
end
|
|
14
|
+
class TransportError < Error
|
|
15
|
+
end
|
|
16
|
+
class TimeoutError < TransportError
|
|
17
|
+
end
|
|
18
|
+
class ConnectionError < TransportError
|
|
19
|
+
end
|
|
20
|
+
class UnexpectedResponseError < Error
|
|
21
|
+
attr_reader body: untyped
|
|
22
|
+
def initialize: (String message, ?body: untyped) -> void
|
|
23
|
+
end
|
|
13
24
|
|
|
14
25
|
class APIError < Error
|
|
15
26
|
attr_reader status: Integer
|
|
16
27
|
attr_reader body: untyped
|
|
17
28
|
attr_reader errors: Array[untyped]
|
|
18
29
|
attr_reader response: untyped
|
|
19
|
-
|
|
30
|
+
attr_reader method: Symbol?
|
|
31
|
+
attr_reader path: String?
|
|
32
|
+
def initialize: (?String? message, status: Integer, ?body: untyped, ?response: untyped, ?method: Symbol?, ?path: String?) -> void
|
|
20
33
|
end
|
|
21
34
|
class AuthenticationError < APIError
|
|
22
35
|
end
|
|
@@ -24,11 +37,15 @@ module Kit
|
|
|
24
37
|
end
|
|
25
38
|
class NotFoundError < APIError
|
|
26
39
|
end
|
|
40
|
+
class ConflictError < APIError
|
|
41
|
+
end
|
|
42
|
+
class PayloadTooLargeError < APIError
|
|
43
|
+
end
|
|
27
44
|
class UnprocessableEntityError < APIError
|
|
28
45
|
end
|
|
29
46
|
class RateLimitError < APIError
|
|
30
47
|
attr_reader retry_after: Integer?
|
|
31
|
-
def initialize: (?String? message, status: Integer, ?body: untyped, ?response: untyped, ?retry_after: Integer?) -> void
|
|
48
|
+
def initialize: (?String? message, status: Integer, ?body: untyped, ?response: untyped, ?method: Symbol?, ?path: String?, ?retry_after: Integer?) -> void
|
|
32
49
|
end
|
|
33
50
|
class ServerError < APIError
|
|
34
51
|
end
|
|
@@ -57,6 +74,8 @@ module Kit
|
|
|
57
74
|
attr_reader scope: String?
|
|
58
75
|
attr_reader created_at: Integer?
|
|
59
76
|
def self.from: (Hash[String, untyped] hash) -> Token
|
|
77
|
+
def inspect: () -> String
|
|
78
|
+
def to_s: () -> String
|
|
60
79
|
def expires_at: () -> Integer?
|
|
61
80
|
def expired?: (?now: Integer, ?leeway: Integer) -> bool
|
|
62
81
|
end
|
|
@@ -72,18 +91,70 @@ module Kit
|
|
|
72
91
|
end
|
|
73
92
|
end
|
|
74
93
|
|
|
94
|
+
module Webhooks
|
|
95
|
+
class SignatureError < Error
|
|
96
|
+
end
|
|
97
|
+
module Signature
|
|
98
|
+
HEADER: String
|
|
99
|
+
SCHEME: String
|
|
100
|
+
DEFAULT_TOLERANCE: Integer
|
|
101
|
+
class Parsed
|
|
102
|
+
attr_reader timestamp: Integer
|
|
103
|
+
attr_reader signatures: Array[String]
|
|
104
|
+
end
|
|
105
|
+
def self.compute: (String secret, Integer | String timestamp, String payload) -> String
|
|
106
|
+
def self.parse: (String? header) -> Parsed
|
|
107
|
+
def self.verify?: (String payload, String? header, secret: String | Array[String], ?tolerance: Integer?, ?now: Integer) -> bool
|
|
108
|
+
def self.verify!: (String payload, String? header, secret: String | Array[String], ?tolerance: Integer?, ?now: Integer) -> bool
|
|
109
|
+
end
|
|
110
|
+
class Event
|
|
111
|
+
attr_reader id: String?
|
|
112
|
+
attr_reader type: String?
|
|
113
|
+
attr_reader created: String?
|
|
114
|
+
attr_reader data: Hash[String, untyped]
|
|
115
|
+
def self.from: (Hash[String, untyped] hash) -> Event
|
|
116
|
+
end
|
|
117
|
+
module Events
|
|
118
|
+
DATA_KEYS: Hash[String, Array[String]]
|
|
119
|
+
PLANNED: Array[String]
|
|
120
|
+
ALL: Array[String]
|
|
121
|
+
AVAILABLE: Array[String]
|
|
122
|
+
end
|
|
123
|
+
module LegacyEvents
|
|
124
|
+
REQUIRED_PARAM: Hash[String, Symbol]
|
|
125
|
+
ALL: Array[String]
|
|
126
|
+
end
|
|
127
|
+
DELIVERY_HEADER: String
|
|
128
|
+
class Delivery
|
|
129
|
+
attr_reader delivery_id: Integer?
|
|
130
|
+
attr_reader events: Array[Event]
|
|
131
|
+
def self.from: (Hash[String, untyped] hash) -> Delivery
|
|
132
|
+
def self.parse: (String payload) -> Delivery
|
|
133
|
+
def self.from_request: (String payload, String? signature_header, secret: String | Array[String], ?tolerance: Integer?, ?now: Integer) -> Delivery
|
|
134
|
+
def type: () -> String?
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
75
138
|
module Auth
|
|
139
|
+
module Credential
|
|
140
|
+
MASK: String
|
|
141
|
+
VISIBLE: Integer
|
|
142
|
+
def self.mask: (untyped secret) -> String
|
|
143
|
+
end
|
|
144
|
+
|
|
76
145
|
class ApiKey
|
|
77
146
|
HEADER: String
|
|
78
147
|
def initialize: (String key) -> void
|
|
79
148
|
def headers: () -> Hash[String, String]
|
|
149
|
+
def inspect: () -> String
|
|
150
|
+
def to_s: () -> String
|
|
80
151
|
end
|
|
81
152
|
|
|
82
153
|
class OAuth
|
|
83
|
-
AUTHORIZE_URL: String
|
|
84
|
-
TOKEN_URL: String
|
|
85
154
|
def initialize: (String access_token) -> void
|
|
86
155
|
def headers: () -> Hash[String, String]
|
|
156
|
+
def inspect: () -> String
|
|
157
|
+
def to_s: () -> String
|
|
87
158
|
end
|
|
88
159
|
end
|
|
89
160
|
|
|
@@ -100,9 +171,12 @@ module Kit
|
|
|
100
171
|
|
|
101
172
|
class Connection
|
|
102
173
|
JSON_TYPE: String
|
|
103
|
-
RETRYABLE: Array[singleton(
|
|
174
|
+
RETRYABLE: Array[singleton(Error)]
|
|
175
|
+
IDEMPOTENT: Array[Symbol]
|
|
104
176
|
def initialize: (Configuration config) -> void
|
|
105
177
|
def request: (Symbol method, String path, ?params: Hash[untyped, untyped], ?body: Hash[untyped, untyped]?) -> untyped
|
|
178
|
+
def request_with_status: (Symbol method, String path, ?params: Hash[untyped, untyped], ?body: Hash[untyped, untyped]?) -> [Integer, untyped]
|
|
179
|
+
def inspect: () -> String
|
|
106
180
|
end
|
|
107
181
|
|
|
108
182
|
class Pagination
|
|
@@ -111,11 +185,20 @@ module Kit
|
|
|
111
185
|
attr_reader start_cursor: String?
|
|
112
186
|
attr_reader end_cursor: String?
|
|
113
187
|
attr_reader per_page: Integer?
|
|
188
|
+
attr_reader total_count: Integer?
|
|
114
189
|
def self.from: (Hash[String, untyped] hash) -> Pagination
|
|
115
190
|
end
|
|
116
191
|
|
|
117
192
|
class Collection[T]
|
|
118
193
|
include Enumerable[T]
|
|
194
|
+
FIRST_PAGE_ONLY: Array[Symbol]
|
|
195
|
+
def self.next_page_params: (Hash[Symbol, untyped] params, String? after) -> Hash[Symbol, untyped]
|
|
196
|
+
def total_count: () -> Integer?
|
|
197
|
+
def size: () -> Integer
|
|
198
|
+
def length: () -> Integer
|
|
199
|
+
def empty?: () -> bool
|
|
200
|
+
def []: (Integer index) -> T?
|
|
201
|
+
def inspect: () -> String
|
|
119
202
|
attr_reader data: Array[T]
|
|
120
203
|
attr_reader pagination: Pagination
|
|
121
204
|
def initialize: (data: Array[T], pagination: Pagination) { (String end_cursor) -> Collection[T] } -> void
|
|
@@ -131,12 +214,40 @@ module Kit
|
|
|
131
214
|
attr_reader id: Integer?
|
|
132
215
|
def self.from: (Hash[String, untyped] hash) -> User
|
|
133
216
|
end
|
|
217
|
+
class Timezone
|
|
218
|
+
attr_reader name: String?
|
|
219
|
+
attr_reader friendly_name: String?
|
|
220
|
+
attr_reader utc_offset: String?
|
|
221
|
+
def self.from: (Hash[String, untyped] hash) -> Timezone
|
|
222
|
+
end
|
|
223
|
+
class Plan
|
|
224
|
+
attr_reader plan_type: String?
|
|
225
|
+
attr_reader interval: String?
|
|
226
|
+
attr_reader subscriber_limit: Integer?
|
|
227
|
+
attr_reader on_trial: bool?
|
|
228
|
+
attr_reader trial_lapse_date: String?
|
|
229
|
+
attr_reader renews_at: String?
|
|
230
|
+
attr_reader cancels_at: String?
|
|
231
|
+
def self.from: (Hash[String, untyped] hash) -> Plan
|
|
232
|
+
end
|
|
233
|
+
class SendingAddress
|
|
234
|
+
attr_reader email_address: String?
|
|
235
|
+
attr_reader from_name: String?
|
|
236
|
+
attr_reader status: String?
|
|
237
|
+
attr_reader is_default: bool?
|
|
238
|
+
attr_reader is_verified: bool?
|
|
239
|
+
attr_reader is_dmarc_configured: bool?
|
|
240
|
+
def self.from: (Hash[String, untyped] hash) -> SendingAddress
|
|
241
|
+
end
|
|
134
242
|
class Account
|
|
135
243
|
attr_reader id: Integer?
|
|
136
244
|
attr_reader name: String?
|
|
137
245
|
attr_reader plan_type: String?
|
|
138
246
|
attr_reader primary_email_address: String?
|
|
139
247
|
attr_reader created_at: String?
|
|
248
|
+
attr_reader timezone: Timezone?
|
|
249
|
+
attr_reader plan: Plan?
|
|
250
|
+
attr_reader sending_addresses: Array[SendingAddress]
|
|
140
251
|
def self.from: (Hash[String, untyped] hash) -> Account
|
|
141
252
|
end
|
|
142
253
|
class AccountInfo
|
|
@@ -157,6 +268,7 @@ module Kit
|
|
|
157
268
|
attr_reader name: String?
|
|
158
269
|
attr_reader created_at: String?
|
|
159
270
|
attr_reader subscriber_count: Integer?
|
|
271
|
+
attr_reader tagged_at: String?
|
|
160
272
|
def self.from: (Hash[String, untyped] hash) -> Tag
|
|
161
273
|
end
|
|
162
274
|
class CustomField
|
|
@@ -164,6 +276,7 @@ module Kit
|
|
|
164
276
|
attr_reader name: String?
|
|
165
277
|
attr_reader key: String?
|
|
166
278
|
attr_reader label: String?
|
|
279
|
+
attr_reader created_at: String?
|
|
167
280
|
def self.from: (Hash[String, untyped] hash) -> CustomField
|
|
168
281
|
end
|
|
169
282
|
class Form
|
|
@@ -236,6 +349,7 @@ module Kit
|
|
|
236
349
|
attr_reader created_by_app: untyped
|
|
237
350
|
attr_reader created_at: String?
|
|
238
351
|
attr_reader previous_secret_expires_at: String?
|
|
352
|
+
attr_reader secret: String?
|
|
239
353
|
def self.from: (Hash[String, untyped] hash) -> WebhookEndpoint
|
|
240
354
|
end
|
|
241
355
|
class EmailTemplate
|
|
@@ -266,6 +380,7 @@ module Kit
|
|
|
266
380
|
attr_reader thumbnail_url: String?
|
|
267
381
|
attr_reader is_paid: bool?
|
|
268
382
|
attr_reader public_url: String?
|
|
383
|
+
attr_reader content: String?
|
|
269
384
|
def self.from: (Hash[String, untyped] hash) -> Post
|
|
270
385
|
end
|
|
271
386
|
class Snippet
|
|
@@ -280,6 +395,19 @@ module Kit
|
|
|
280
395
|
attr_reader document: untyped
|
|
281
396
|
def self.from: (Hash[String, untyped] hash) -> Snippet
|
|
282
397
|
end
|
|
398
|
+
class BulkFailure
|
|
399
|
+
attr_reader item: untyped
|
|
400
|
+
attr_reader errors: Array[String]
|
|
401
|
+
def self.from: (Hash[String, untyped] hash) -> BulkFailure
|
|
402
|
+
end
|
|
403
|
+
class BulkResult
|
|
404
|
+
attr_reader items: Array[Hash[String, untyped]]
|
|
405
|
+
attr_reader failures: Array[BulkFailure]
|
|
406
|
+
attr_reader async: bool
|
|
407
|
+
def self.from: (Integer status, untyped body, String? key) -> BulkResult
|
|
408
|
+
def async?: () -> bool
|
|
409
|
+
def success?: () -> bool
|
|
410
|
+
end
|
|
283
411
|
class Purchase
|
|
284
412
|
attr_reader id: Integer?
|
|
285
413
|
attr_reader transaction_id: String?
|
|
@@ -309,6 +437,7 @@ module Kit
|
|
|
309
437
|
attr_reader delay_unit: String?
|
|
310
438
|
attr_reader send_days: Array[untyped]?
|
|
311
439
|
attr_reader stats: Hash[String, untyped]?
|
|
440
|
+
attr_reader content: String?
|
|
312
441
|
def self.from: (Hash[String, untyped] hash) -> SequenceEmail
|
|
313
442
|
end
|
|
314
443
|
class BroadcastStats
|
|
@@ -385,12 +514,22 @@ module Kit
|
|
|
385
514
|
attr_reader canceled_at: String?
|
|
386
515
|
attr_reader location: Hash[String, untyped]?
|
|
387
516
|
attr_reader fields: Hash[String, untyped]?
|
|
517
|
+
attr_reader added_at: String?
|
|
518
|
+
attr_reader tagged_at: String?
|
|
519
|
+
attr_reader referrer: String?
|
|
520
|
+
attr_reader referrer_utm_parameters: Hash[String, untyped]?
|
|
521
|
+
attr_reader attribution: Hash[String, untyped]?
|
|
522
|
+
attr_reader tags: Array[Hash[String, untyped]]?
|
|
523
|
+
attr_reader tag_names: Array[String]?
|
|
524
|
+
attr_reader tag_ids: Array[Integer]?
|
|
525
|
+
attr_reader stats: Hash[String, untyped]?
|
|
388
526
|
def self.from: (Hash[String, untyped] hash) -> Subscriber
|
|
389
527
|
end
|
|
390
528
|
end
|
|
391
529
|
|
|
392
530
|
module Resources
|
|
393
531
|
class Base
|
|
532
|
+
OMIT: Object
|
|
394
533
|
def initialize: (Connection connection) -> void
|
|
395
534
|
end
|
|
396
535
|
class Account < Base
|
|
@@ -406,11 +545,12 @@ module Kit
|
|
|
406
545
|
def get: (Integer id) -> Objects::Subscriber
|
|
407
546
|
def create: (email_address: String, ?first_name: String?, ?state: String?, ?fields: Hash[untyped, untyped]?) -> Objects::Subscriber
|
|
408
547
|
def update: (Integer id, ?first_name: String?, ?email_address: String?, ?fields: Hash[untyped, untyped]?) -> Objects::Subscriber
|
|
409
|
-
def unsubscribe: (Integer id) -> Objects::Subscriber
|
|
548
|
+
def unsubscribe: (Integer id) -> Objects::Subscriber?
|
|
410
549
|
def filter: (**untyped params) -> Collection[Objects::Subscriber]
|
|
411
550
|
def tags: (Integer id, **untyped params) -> Collection[Objects::Tag]
|
|
412
|
-
def stats: (Integer id) -> Objects::SubscriberStats
|
|
551
|
+
def stats: (Integer id, ?email_sent_after: String?, ?email_sent_before: String?) -> Objects::SubscriberStats
|
|
413
552
|
def set_location: (Integer id, location: Hash[untyped, untyped]) -> Objects::Subscriber
|
|
553
|
+
def update_location: (Integer id, location: Hash[untyped, untyped]) -> Objects::Subscriber
|
|
414
554
|
def remove_location: (Integer id) -> void
|
|
415
555
|
end
|
|
416
556
|
class Tags < Base
|
|
@@ -437,24 +577,24 @@ module Kit
|
|
|
437
577
|
end
|
|
438
578
|
class Sequences < Base
|
|
439
579
|
def list: (**untyped params) -> Collection[Objects::Sequence]
|
|
440
|
-
def get: (Integer id) -> Objects::Sequence
|
|
441
|
-
def create: (name: String,
|
|
442
|
-
def update: (Integer id,
|
|
580
|
+
def get: (Integer id, ?include: String?) -> Objects::Sequence
|
|
581
|
+
def create: (name: String, ?email_address: String?, ?email_template_id: Integer?, ?send_days: Array[String]?, ?send_hour: Integer?, ?time_zone: String?, ?active: bool?, ?repeat: bool?, ?hold: bool?, ?exclude_subscriber_sources: Array[Hash[Symbol | String, untyped]]?) -> Objects::Sequence
|
|
582
|
+
def update: (Integer id, ?name: String?, ?email_address: String?, ?email_template_id: Integer?, ?send_days: Array[String]?, ?send_hour: Integer?, ?time_zone: String?, ?active: bool?, ?repeat: bool?, ?hold: bool?, ?exclude_subscriber_sources: Array[Hash[Symbol | String, untyped]]?) -> Objects::Sequence
|
|
443
583
|
def delete: (Integer id) -> void
|
|
444
584
|
def subscribers: (Integer sequence_id, **untyped params) -> Collection[Objects::Subscriber]
|
|
445
585
|
def add_subscriber: (Integer sequence_id, Integer subscriber_id) -> Objects::Subscriber
|
|
446
586
|
def add_subscriber_by_email: (Integer sequence_id, email_address: String) -> Objects::Subscriber
|
|
447
587
|
def emails: (Integer sequence_id, **untyped params) -> Collection[Objects::SequenceEmail]
|
|
448
|
-
def email: (Integer sequence_id, Integer id) -> Objects::SequenceEmail
|
|
449
|
-
def create_email: (Integer sequence_id,
|
|
450
|
-
def update_email: (Integer sequence_id, Integer id,
|
|
588
|
+
def email: (Integer sequence_id, Integer id, ?include: String?) -> Objects::SequenceEmail
|
|
589
|
+
def create_email: (Integer sequence_id, subject: String, delay_value: Integer, delay_unit: String, ?preview_text: String?, ?content: String?, ?email_template_id: Integer?, ?published: bool?, ?send_days: Array[String]?, ?position: Integer?) -> Objects::SequenceEmail
|
|
590
|
+
def update_email: (Integer sequence_id, Integer id, ?subject: String?, ?delay_value: Integer?, ?delay_unit: String?, ?preview_text: String?, ?content: String?, ?email_template_id: Integer?, ?published: bool?, ?send_days: Array[String]?, ?position: Integer?) -> Objects::SequenceEmail
|
|
451
591
|
def delete_email: (Integer sequence_id, Integer id) -> void
|
|
452
592
|
end
|
|
453
593
|
class Broadcasts < Base
|
|
454
594
|
def list: (**untyped params) -> Collection[Objects::Broadcast]
|
|
455
595
|
def get: (Integer id) -> Objects::Broadcast
|
|
456
|
-
def create: (
|
|
457
|
-
def update: (Integer id,
|
|
596
|
+
def create: (?subject: String?, ?preview_text: String?, ?content: String?, ?description: String?, ?public: bool?, ?published_at: String?, ?send_at: String?, ?email_address: String?, ?email_template_id: Integer?, ?thumbnail_alt: String?, ?thumbnail_url: String?, ?subscriber_filter: Array[Hash[Symbol | String, untyped]]?) -> Objects::Broadcast
|
|
597
|
+
def update: (Integer id, ?subject: String?, ?preview_text: String?, ?content: String?, ?description: String?, ?public: bool?, ?published_at: String?, ?send_at: String?, ?email_address: String?, ?email_template_id: Integer?, ?thumbnail_alt: String?, ?thumbnail_url: String?, ?subscriber_filter: Array[Hash[Symbol | String, untyped]]?) -> Objects::Broadcast
|
|
458
598
|
def delete: (Integer id) -> void
|
|
459
599
|
def stats_list: (**untyped params) -> Collection[Objects::BroadcastStats]
|
|
460
600
|
def stats: (Integer id) -> Objects::BroadcastStats
|
|
@@ -470,23 +610,23 @@ module Kit
|
|
|
470
610
|
class Purchases < Base
|
|
471
611
|
def list: (**untyped params) -> Collection[Objects::Purchase]
|
|
472
612
|
def get: (Integer id) -> Objects::Purchase
|
|
473
|
-
def create: (
|
|
613
|
+
def create: (email_address: String, transaction_id: String, status: String, currency: String, ?transaction_time: String?, ?subtotal: Numeric?, ?tax: Numeric?, ?shipping: Numeric?, ?discount: Numeric?, ?total: Numeric?, ?products: Array[Hash[Symbol | String, untyped]]?) -> Objects::Purchase
|
|
474
614
|
end
|
|
475
615
|
class Bulk < Base
|
|
476
|
-
def create_subscribers: (Array[untyped] subscribers, ?callback_url: String?) ->
|
|
477
|
-
def create_custom_fields: (Array[untyped] custom_fields, ?callback_url: String?) ->
|
|
478
|
-
def update_custom_field_values: (Array[untyped] custom_field_values, ?callback_url: String?) ->
|
|
479
|
-
def add_subscribers_to_forms: (Array[untyped] additions, ?callback_url: String?) ->
|
|
480
|
-
def create_tags: (Array[untyped] tags, ?callback_url: String?) ->
|
|
481
|
-
def delete_tags: (Array[untyped] tags, ?callback_url: String?) ->
|
|
482
|
-
def tag_subscribers: (Array[untyped] taggings, ?callback_url: String?) ->
|
|
483
|
-
def remove_tag_subscribers: (Array[untyped] taggings, ?callback_url: String?) ->
|
|
616
|
+
def create_subscribers: (Array[untyped] subscribers, ?callback_url: String?) -> Objects::BulkResult
|
|
617
|
+
def create_custom_fields: (Array[untyped] custom_fields, ?callback_url: String?) -> Objects::BulkResult
|
|
618
|
+
def update_custom_field_values: (Array[untyped] custom_field_values, ?callback_url: String?) -> Objects::BulkResult
|
|
619
|
+
def add_subscribers_to_forms: (Array[untyped] additions, ?callback_url: String?) -> Objects::BulkResult
|
|
620
|
+
def create_tags: (Array[untyped] tags, ?callback_url: String?) -> Objects::BulkResult
|
|
621
|
+
def delete_tags: (Array[untyped] tags, ?callback_url: String?) -> Objects::BulkResult
|
|
622
|
+
def tag_subscribers: (Array[untyped] taggings, ?callback_url: String?) -> Objects::BulkResult
|
|
623
|
+
def remove_tag_subscribers: (Array[untyped] taggings, ?callback_url: String?) -> Objects::BulkResult
|
|
484
624
|
end
|
|
485
625
|
class Snippets < Base
|
|
486
626
|
def list: (**untyped params) -> Collection[Objects::Snippet]
|
|
487
627
|
def get: (Integer id) -> Objects::Snippet
|
|
488
|
-
def create: (
|
|
489
|
-
def update: (Integer id,
|
|
628
|
+
def create: (name: String, snippet_type: String, ?content: String?, ?document_attributes: Hash[Symbol | String, untyped]?) -> Objects::Snippet
|
|
629
|
+
def update: (Integer id, ?name: String?, ?archived: bool?, ?content: String?, ?document_attributes: Hash[Symbol | String, untyped]?) -> Objects::Snippet
|
|
490
630
|
end
|
|
491
631
|
class Segments < Base
|
|
492
632
|
def list: (**untyped params) -> Collection[Objects::Segment]
|
|
@@ -500,6 +640,7 @@ module Kit
|
|
|
500
640
|
def list: (**untyped params) -> Collection[Objects::WebhookEndpoint]
|
|
501
641
|
def get: (Integer id) -> Objects::WebhookEndpoint
|
|
502
642
|
def create: (url: String, events: Array[untyped], ?name: String?, ?description: String?) -> Objects::WebhookEndpoint
|
|
643
|
+
def update: (Integer id, ?name: String?, ?url: String?, ?description: String?, ?status: String?, ?events: Array[String]?) -> Objects::WebhookEndpoint
|
|
503
644
|
def delete: (Integer id) -> void
|
|
504
645
|
def rotate_secret: (Integer id, ?force: bool?) -> Objects::WebhookEndpoint
|
|
505
646
|
def revoke_previous_secret: (Integer id) -> Objects::WebhookEndpoint
|
|
@@ -508,7 +649,7 @@ module Kit
|
|
|
508
649
|
|
|
509
650
|
class Client
|
|
510
651
|
attr_reader config: Configuration
|
|
511
|
-
def initialize: (?api_key: String?, ?access_token: String?,
|
|
652
|
+
def initialize: (?api_key: String?, ?access_token: String?, ?base_url: String, ?open_timeout: Integer, ?read_timeout: Integer, ?max_retries: Integer, ?retry_backoff: Float, ?max_backoff: Numeric) -> void
|
|
512
653
|
def account: () -> Resources::Account
|
|
513
654
|
def subscribers: () -> Resources::Subscribers
|
|
514
655
|
def tags: () -> Resources::Tags
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: kit-rb
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Lawrence Lin
|
|
@@ -31,14 +31,18 @@ executables: []
|
|
|
31
31
|
extensions: []
|
|
32
32
|
extra_rdoc_files: []
|
|
33
33
|
files:
|
|
34
|
+
- ".githooks/pre-commit"
|
|
35
|
+
- ".githooks/pre-push"
|
|
34
36
|
- CHANGELOG.md
|
|
35
37
|
- LICENSE.txt
|
|
36
38
|
- README.md
|
|
37
39
|
- Rakefile
|
|
38
40
|
- Steepfile
|
|
39
41
|
- docs/DESIGN.md
|
|
42
|
+
- docs/TASKS.md
|
|
40
43
|
- lib/kit-rb.rb
|
|
41
44
|
- lib/kit/auth/api_key.rb
|
|
45
|
+
- lib/kit/auth/credential.rb
|
|
42
46
|
- lib/kit/auth/oauth.rb
|
|
43
47
|
- lib/kit/client.rb
|
|
44
48
|
- lib/kit/configuration.rb
|
|
@@ -51,6 +55,7 @@ files:
|
|
|
51
55
|
- lib/kit/objects/broadcast.rb
|
|
52
56
|
- lib/kit/objects/broadcast_click.rb
|
|
53
57
|
- lib/kit/objects/broadcast_stats.rb
|
|
58
|
+
- lib/kit/objects/bulk_result.rb
|
|
54
59
|
- lib/kit/objects/creator_profile.rb
|
|
55
60
|
- lib/kit/objects/custom_field.rb
|
|
56
61
|
- lib/kit/objects/email_stats.rb
|
|
@@ -86,6 +91,9 @@ files:
|
|
|
86
91
|
- lib/kit/resources/webhook_endpoints.rb
|
|
87
92
|
- lib/kit/resources/webhooks.rb
|
|
88
93
|
- lib/kit/version.rb
|
|
94
|
+
- lib/kit/webhooks/delivery.rb
|
|
95
|
+
- lib/kit/webhooks/events.rb
|
|
96
|
+
- lib/kit/webhooks/signature.rb
|
|
89
97
|
- sig/kit-rb.rbs
|
|
90
98
|
homepage: https://github.com/linyiru/kit-rb
|
|
91
99
|
licenses:
|
|
@@ -94,6 +102,8 @@ metadata:
|
|
|
94
102
|
homepage_uri: https://github.com/linyiru/kit-rb
|
|
95
103
|
source_code_uri: https://github.com/linyiru/kit-rb
|
|
96
104
|
changelog_uri: https://github.com/linyiru/kit-rb/blob/main/CHANGELOG.md
|
|
105
|
+
bug_tracker_uri: https://github.com/linyiru/kit-rb/issues
|
|
106
|
+
documentation_uri: https://github.com/linyiru/kit-rb#readme
|
|
97
107
|
rubygems_mfa_required: 'true'
|
|
98
108
|
rdoc_options: []
|
|
99
109
|
require_paths:
|