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.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/.githooks/pre-commit +21 -0
  3. data/.githooks/pre-push +6 -0
  4. data/CHANGELOG.md +70 -0
  5. data/README.md +58 -16
  6. data/docs/DESIGN.md +38 -14
  7. data/docs/TASKS.md +47 -0
  8. data/lib/kit/auth/api_key.rb +7 -0
  9. data/lib/kit/auth/credential.rb +22 -0
  10. data/lib/kit/auth/oauth.rb +7 -8
  11. data/lib/kit/connection.rb +53 -20
  12. data/lib/kit/errors.rb +52 -15
  13. data/lib/kit/oauth/token.rb +9 -0
  14. data/lib/kit/objects/account.rb +44 -3
  15. data/lib/kit/objects/bulk_result.rb +39 -0
  16. data/lib/kit/objects/custom_field.rb +4 -2
  17. data/lib/kit/objects/post.rb +4 -2
  18. data/lib/kit/objects/sequence_email.rb +5 -2
  19. data/lib/kit/objects/subscriber.rb +16 -9
  20. data/lib/kit/objects/tag.rb +5 -3
  21. data/lib/kit/objects/webhook_endpoint.rb +7 -2
  22. data/lib/kit/pagination.rb +47 -3
  23. data/lib/kit/resources/account.rb +4 -4
  24. data/lib/kit/resources/base.rb +46 -4
  25. data/lib/kit/resources/broadcasts.rb +32 -13
  26. data/lib/kit/resources/bulk.rb +18 -20
  27. data/lib/kit/resources/custom_fields.rb +2 -2
  28. data/lib/kit/resources/forms.rb +3 -3
  29. data/lib/kit/resources/posts.rb +1 -1
  30. data/lib/kit/resources/purchases.rb +10 -6
  31. data/lib/kit/resources/sequences.rb +49 -25
  32. data/lib/kit/resources/snippets.rb +13 -8
  33. data/lib/kit/resources/subscribers.rb +23 -9
  34. data/lib/kit/resources/tags.rb +6 -6
  35. data/lib/kit/resources/webhook_endpoints.rb +12 -4
  36. data/lib/kit/resources/webhooks.rb +1 -1
  37. data/lib/kit/version.rb +1 -1
  38. data/lib/kit/webhooks/delivery.rb +61 -0
  39. data/lib/kit/webhooks/events.rb +113 -0
  40. data/lib/kit/webhooks/signature.rb +100 -0
  41. data/lib/kit-rb.rb +5 -0
  42. data/sig/kit-rb.rbs +170 -29
  43. 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 (P0: account slice).
2
- # Expanded per-resource as coverage grows in P1/P2.
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
- def initialize: (?String? message, status: Integer, ?body: untyped, ?response: untyped) -> void
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(APIError)]
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, **untyped attributes) -> Objects::Sequence
442
- def update: (Integer id, **untyped attributes) -> Objects::Sequence
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, **untyped attributes) -> Objects::SequenceEmail
450
- def update_email: (Integer sequence_id, Integer id, **untyped attributes) -> Objects::SequenceEmail
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: (**untyped attributes) -> Objects::Broadcast
457
- def update: (Integer id, **untyped attributes) -> Objects::Broadcast
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: (**untyped attributes) -> Objects::Purchase
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?) -> Hash[String, untyped]
477
- def create_custom_fields: (Array[untyped] custom_fields, ?callback_url: String?) -> Hash[String, untyped]
478
- def update_custom_field_values: (Array[untyped] custom_field_values, ?callback_url: String?) -> Hash[String, untyped]
479
- def add_subscribers_to_forms: (Array[untyped] additions, ?callback_url: String?) -> Hash[String, untyped]
480
- def create_tags: (Array[untyped] tags, ?callback_url: String?) -> Hash[String, untyped]
481
- def delete_tags: (Array[untyped] tags, ?callback_url: String?) -> Hash[String, untyped]
482
- def tag_subscribers: (Array[untyped] taggings, ?callback_url: String?) -> Hash[String, untyped]
483
- def remove_tag_subscribers: (Array[untyped] taggings, ?callback_url: String?) -> Hash[String, untyped]
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: (**untyped attributes) -> Objects::Snippet
489
- def update: (Integer id, **untyped attributes) -> Objects::Snippet
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?, **untyped options) -> void
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.2.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: