userlist 0.9.0 → 1.1.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/.github/workflows/test.yml +3 -1
- data/.rubocop.yml +1 -1
- data/CHANGELOG.md +14 -0
- data/Gemfile +7 -0
- data/README.md +18 -1
- data/lib/userlist/config.rb +2 -2
- data/lib/userlist/delivery_method.rb +55 -0
- data/lib/userlist/push/client.rb +21 -4
- data/lib/userlist/push/company.rb +1 -1
- data/lib/userlist/push/event.rb +1 -1
- data/lib/userlist/push/message.rb +20 -0
- data/lib/userlist/push/operations/delete.rb +2 -1
- data/lib/userlist/push/operations/{create.rb → push.rb} +6 -8
- data/lib/userlist/push/relationship.rb +1 -1
- data/lib/userlist/push/resource.rb +21 -14
- data/lib/userlist/push/serializer.rb +25 -12
- data/lib/userlist/push/strategies/active_job/worker.rb +2 -0
- data/lib/userlist/push/strategies/active_job.rb +1 -1
- data/lib/userlist/push/strategies/direct.rb +1 -5
- data/lib/userlist/push/strategies/sidekiq.rb +1 -1
- data/lib/userlist/push/strategies/threaded/worker.rb +7 -13
- data/lib/userlist/push/user.rb +1 -1
- data/lib/userlist/push.rb +18 -9
- data/lib/userlist/retryable.rb +16 -4
- data/lib/userlist/version.rb +1 -1
- data/lib/userlist.rb +18 -1
- data/userlist.gemspec +2 -8
- metadata +9 -81
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9ed2e089f640ba0d4865236a53b5744e9edfbbe4400337b789c6e09c80474870
|
|
4
|
+
data.tar.gz: ce7ad3b24cc47645ee91d69aa27f312628f840c427c52f49ae38823abf9ad1d8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6c0b7d45d3edcb1a07e10e759b6d3911ff594f6feceadc7fb17eef9961ea693853e570e9223ce837cc11cc04b025fd227ed42ba4c5e35501406f9a636d39f22b
|
|
7
|
+
data.tar.gz: 64146b1ba4fda2069e74e230e81c7d1447166b03775fe048ba821d7ec04702ab69ea85a116ffb0243f4f3a4e01b83e7e53c28fef7813f9351851428bd6343743
|
data/.github/workflows/test.yml
CHANGED
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Userlist for Ruby Changelog
|
|
2
2
|
|
|
3
|
+
## Unreleased (main)
|
|
4
|
+
|
|
5
|
+
## v1.1.0 (2025-10-17)
|
|
6
|
+
|
|
7
|
+
- Allow specifying companies in messages
|
|
8
|
+
- Add reply_to support to DeliveryMethod for ActionMailer integration
|
|
9
|
+
- Fixes issue with deleting resources when they are not pushed by default
|
|
10
|
+
|
|
11
|
+
## v1.0.0 (2025-04-08)
|
|
12
|
+
|
|
13
|
+
- Updates ActiveJob Worker to retry on errors with polynomially longer wait times, up to 10 attempts
|
|
14
|
+
- Improve internal error handling to rely on exceptions
|
|
15
|
+
- Adds support for messages
|
|
16
|
+
|
|
3
17
|
## v0.9.0 (2024-03-19)
|
|
4
18
|
|
|
5
19
|
- Allows deleteing resources by using other identifiers (like email)
|
data/Gemfile
CHANGED
|
@@ -4,6 +4,11 @@ git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
|
|
|
4
4
|
|
|
5
5
|
gemspec
|
|
6
6
|
|
|
7
|
+
gem 'jwt', '~> 2.2'
|
|
8
|
+
gem 'rake', '~> 13.0'
|
|
9
|
+
gem 'rspec', '~> 3.0'
|
|
10
|
+
gem 'webmock', '~> 3.18'
|
|
11
|
+
|
|
7
12
|
gem 'guard-rspec', '~> 4.7'
|
|
8
13
|
gem 'guard-rubocop', '~> 1.3'
|
|
9
14
|
gem 'rubocop', '~> 1.45'
|
|
@@ -11,3 +16,5 @@ gem 'rubocop', '~> 1.45'
|
|
|
11
16
|
gem 'sidekiq'
|
|
12
17
|
gem 'activejob'
|
|
13
18
|
gem 'uri'
|
|
19
|
+
gem 'irb'
|
|
20
|
+
gem 'mail'
|
data/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Userlist for Ruby
|
|
1
|
+
# Userlist for Ruby [](https://github.com/userlist/userlist-ruby)
|
|
2
2
|
|
|
3
3
|
This gem helps with integrating [Userlist](https://userlist.com) into Ruby applications.
|
|
4
4
|
|
|
@@ -186,6 +186,23 @@ Userlist::Push.events.push(
|
|
|
186
186
|
)
|
|
187
187
|
```
|
|
188
188
|
|
|
189
|
+
### Sending Transactional Messages
|
|
190
|
+
|
|
191
|
+
To send transactional messages, use the `Userlist::Push.messages.push` method.
|
|
192
|
+
|
|
193
|
+
```ruby
|
|
194
|
+
message = {
|
|
195
|
+
user: 'user-1',
|
|
196
|
+
template: 'welcome-email',
|
|
197
|
+
properties: {
|
|
198
|
+
account_name: 'Example, Inc.',
|
|
199
|
+
billing_plan: 'Pro'
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
Userlist::Push.messages.push(message)
|
|
204
|
+
```
|
|
205
|
+
|
|
189
206
|
### Tokens for in-app messages
|
|
190
207
|
|
|
191
208
|
In order to use in-app messages, you must create a JWT token for the currently signed in user on the server side. To do this, please configure both the `push_key` and the `push_id` configuration variables. Afterwards, you can use the `Userlist::Token.generate` method to get a signed token for the given user identifier.
|
data/lib/userlist/config.rb
CHANGED
|
@@ -75,12 +75,12 @@ module Userlist
|
|
|
75
75
|
key?(name.to_sym) || super
|
|
76
76
|
end
|
|
77
77
|
|
|
78
|
-
def method_missing(name,
|
|
78
|
+
def method_missing(name, ...)
|
|
79
79
|
if respond_to_missing?(name)
|
|
80
80
|
name = name.to_s
|
|
81
81
|
method = name =~ /=$/ ? :[]= : :[]
|
|
82
82
|
name = name.sub(/=$/, '').to_sym
|
|
83
|
-
send(method, name,
|
|
83
|
+
send(method, name, ...)
|
|
84
84
|
else
|
|
85
85
|
super
|
|
86
86
|
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
class Userlist::DeliveryMethod
|
|
2
|
+
attr_reader :userlist, :settings
|
|
3
|
+
|
|
4
|
+
def initialize(settings = {})
|
|
5
|
+
@settings = settings
|
|
6
|
+
|
|
7
|
+
@userlist = Userlist::Push.new(settings)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def deliver!(mail)
|
|
11
|
+
message = serialize(mail)
|
|
12
|
+
|
|
13
|
+
userlist.messages.push(message.merge(theme: nil))
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
def serialize(mail)
|
|
19
|
+
{
|
|
20
|
+
to: serialize_address(mail.to),
|
|
21
|
+
from: serialize_address(mail.from),
|
|
22
|
+
reply_to: serialize_address(mail.reply_to),
|
|
23
|
+
subject: mail.subject,
|
|
24
|
+
body: serialize_body(mail.body)
|
|
25
|
+
}.compact
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def serialize_address(address)
|
|
29
|
+
return if address.nil? || Array(address).empty?
|
|
30
|
+
|
|
31
|
+
Array(address).map(&:to_s)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def serialize_body(body)
|
|
35
|
+
return if body.nil?
|
|
36
|
+
|
|
37
|
+
if body.multipart?
|
|
38
|
+
parts = body.parts.filter_map { |part| serialize_part(part) }
|
|
39
|
+
|
|
40
|
+
return parts.first if parts.size == 1
|
|
41
|
+
|
|
42
|
+
{ type: :multipart, content: parts }
|
|
43
|
+
else
|
|
44
|
+
{ type: :text, content: body.decoded }
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def serialize_part(part)
|
|
49
|
+
if part.content_type.start_with?('text/html')
|
|
50
|
+
{ type: :html, content: part.decoded }
|
|
51
|
+
elsif part.content_type.start_with?('text/plain')
|
|
52
|
+
{ type: :text, content: part.decoded }
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
data/lib/userlist/push/client.rb
CHANGED
|
@@ -49,18 +49,35 @@ module Userlist
|
|
|
49
49
|
end
|
|
50
50
|
|
|
51
51
|
def request(method, path, payload = nil)
|
|
52
|
+
request = build_request(method, path, payload)
|
|
53
|
+
|
|
54
|
+
logger.debug "Sending #{request.method} to #{URI.join(endpoint, request.path)} with body #{request.body}"
|
|
55
|
+
|
|
56
|
+
response = process_request(request)
|
|
57
|
+
|
|
58
|
+
logger.debug "Recieved #{response.code} #{response.message} with body #{response.body}"
|
|
59
|
+
|
|
60
|
+
handle_response(response)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def build_request(method, path, payload)
|
|
52
64
|
request = method.new(path)
|
|
53
65
|
request['Accept'] = 'application/json'
|
|
54
66
|
request['Authorization'] = "Push #{token}"
|
|
55
67
|
request['Content-Type'] = 'application/json; charset=UTF-8'
|
|
56
68
|
request.body = JSON.generate(payload) if payload
|
|
69
|
+
request
|
|
70
|
+
end
|
|
57
71
|
|
|
58
|
-
|
|
59
|
-
|
|
72
|
+
def process_request(request)
|
|
60
73
|
http.start unless http.started?
|
|
61
|
-
|
|
74
|
+
http.request(request)
|
|
75
|
+
rescue Timeout::Error => e
|
|
76
|
+
raise Userlist::TimeoutError, e.message
|
|
77
|
+
end
|
|
62
78
|
|
|
63
|
-
|
|
79
|
+
def handle_response(response)
|
|
80
|
+
raise(Userlist::RequestError, response) if response.code.to_i >= 400
|
|
64
81
|
|
|
65
82
|
response
|
|
66
83
|
end
|
data/lib/userlist/push/event.rb
CHANGED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module Userlist
|
|
2
|
+
class Push
|
|
3
|
+
class Message < Resource
|
|
4
|
+
include Operations::Push
|
|
5
|
+
|
|
6
|
+
has_one :user, type: 'Userlist::Push::User'
|
|
7
|
+
has_one :company, type: 'Userlist::Push::Company'
|
|
8
|
+
|
|
9
|
+
def initialize(payload = {}, config = Userlist.config)
|
|
10
|
+
raise Userlist::ArgumentError, 'Missing required payload' unless payload
|
|
11
|
+
|
|
12
|
+
super
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def push?
|
|
16
|
+
super && (user.nil? || user.push?)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -5,8 +5,9 @@ module Userlist
|
|
|
5
5
|
module ClassMethods
|
|
6
6
|
def delete(payload = {}, config = self.config)
|
|
7
7
|
return false unless resource = from_payload(payload, config)
|
|
8
|
+
return false unless resource.delete?
|
|
8
9
|
|
|
9
|
-
strategy.call(:delete, endpoint, resource
|
|
10
|
+
strategy.call(:delete, endpoint, resource.for_context(:delete))
|
|
10
11
|
end
|
|
11
12
|
end
|
|
12
13
|
|
|
@@ -1,24 +1,22 @@
|
|
|
1
1
|
module Userlist
|
|
2
2
|
class Push
|
|
3
3
|
module Operations
|
|
4
|
-
module
|
|
4
|
+
module Push
|
|
5
5
|
module ClassMethods
|
|
6
|
-
def
|
|
6
|
+
def push(payload = {}, config = self.config)
|
|
7
7
|
return false unless resource = from_payload(payload, config)
|
|
8
|
+
return false unless resource.push?
|
|
8
9
|
|
|
9
|
-
strategy.call(:post, endpoint, resource)
|
|
10
|
+
strategy.call(:post, endpoint, resource.for_context(:push))
|
|
10
11
|
end
|
|
11
12
|
|
|
12
|
-
alias push
|
|
13
|
+
alias create push
|
|
14
|
+
alias update push
|
|
13
15
|
end
|
|
14
16
|
|
|
15
17
|
def self.included(base)
|
|
16
18
|
base.extend(ClassMethods)
|
|
17
19
|
end
|
|
18
|
-
|
|
19
|
-
def create?
|
|
20
|
-
push?
|
|
21
|
-
end
|
|
22
20
|
end
|
|
23
21
|
end
|
|
24
22
|
end
|
|
@@ -19,18 +19,18 @@ module Userlist
|
|
|
19
19
|
new(payload, config)
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
-
def
|
|
23
|
-
@
|
|
22
|
+
def association_names
|
|
23
|
+
@association_names ||= associations.keys
|
|
24
24
|
end
|
|
25
25
|
|
|
26
|
-
def
|
|
27
|
-
@
|
|
26
|
+
def associations
|
|
27
|
+
@associations ||= {}
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
protected
|
|
31
31
|
|
|
32
32
|
def has_one(name, type:) # rubocop:disable Naming/PredicateName
|
|
33
|
-
|
|
33
|
+
associations[name.to_sym] = { type: type }
|
|
34
34
|
|
|
35
35
|
generated_methods.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
36
36
|
def #{name} # def company
|
|
@@ -40,14 +40,14 @@ module Userlist
|
|
|
40
40
|
end
|
|
41
41
|
|
|
42
42
|
def has_many(name, **options) # rubocop:disable Naming/PredicateName
|
|
43
|
-
|
|
43
|
+
associations[name.to_sym] = options
|
|
44
44
|
|
|
45
45
|
generated_methods.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
46
46
|
def #{name} # def companies
|
|
47
|
-
|
|
47
|
+
associations = self.class.associations[:#{name}] # associations = self.class.associations[:companies]
|
|
48
48
|
#
|
|
49
|
-
ResourceCollection.new(payload[:#{name}],
|
|
50
|
-
end #
|
|
49
|
+
ResourceCollection.new(payload[:#{name}], associations, self, config) # ResourceCollection.new(payload[:companies], associations, self, config)
|
|
50
|
+
end # end
|
|
51
51
|
RUBY
|
|
52
52
|
end
|
|
53
53
|
|
|
@@ -58,13 +58,14 @@ module Userlist
|
|
|
58
58
|
end
|
|
59
59
|
end
|
|
60
60
|
|
|
61
|
-
attr_reader :payload, :config
|
|
61
|
+
attr_reader :payload, :config, :context
|
|
62
62
|
|
|
63
63
|
def initialize(payload = {}, config = Userlist.config)
|
|
64
64
|
raise Userlist::ArgumentError, 'Missing required payload' unless payload
|
|
65
65
|
|
|
66
66
|
@payload = payload
|
|
67
67
|
@config = config
|
|
68
|
+
@context = :push
|
|
68
69
|
end
|
|
69
70
|
|
|
70
71
|
def respond_to_missing?(method, include_private = false)
|
|
@@ -73,7 +74,7 @@ module Userlist
|
|
|
73
74
|
end
|
|
74
75
|
|
|
75
76
|
def to_hash
|
|
76
|
-
Serializer.serialize(self)
|
|
77
|
+
Serializer.serialize(self, context: context)
|
|
77
78
|
end
|
|
78
79
|
alias to_h to_hash
|
|
79
80
|
|
|
@@ -95,17 +96,23 @@ module Userlist
|
|
|
95
96
|
alias == eql?
|
|
96
97
|
|
|
97
98
|
def attribute_names
|
|
98
|
-
payload.keys.map(&:to_sym) -
|
|
99
|
+
payload.keys.map(&:to_sym) - association_names
|
|
99
100
|
end
|
|
100
101
|
|
|
101
|
-
def
|
|
102
|
-
self.class.
|
|
102
|
+
def association_names
|
|
103
|
+
self.class.association_names.to_a
|
|
103
104
|
end
|
|
104
105
|
|
|
105
106
|
def push?
|
|
106
107
|
true
|
|
107
108
|
end
|
|
108
109
|
|
|
110
|
+
def for_context(context)
|
|
111
|
+
dup.tap do |instance|
|
|
112
|
+
instance.instance_variable_set(:@context, context)
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
109
116
|
private
|
|
110
117
|
|
|
111
118
|
def method_missing(method, *args, &block)
|
|
@@ -3,8 +3,14 @@ require 'set'
|
|
|
3
3
|
module Userlist
|
|
4
4
|
class Push
|
|
5
5
|
class Serializer
|
|
6
|
-
def self.serialize(resource)
|
|
7
|
-
new.serialize(resource)
|
|
6
|
+
def self.serialize(resource, **options)
|
|
7
|
+
new(**options).serialize(resource)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
attr_reader :context
|
|
11
|
+
|
|
12
|
+
def initialize(context:)
|
|
13
|
+
@context = context
|
|
8
14
|
end
|
|
9
15
|
|
|
10
16
|
def serialize(resource)
|
|
@@ -12,6 +18,13 @@ module Userlist
|
|
|
12
18
|
resource
|
|
13
19
|
end
|
|
14
20
|
|
|
21
|
+
def serialize?(resource)
|
|
22
|
+
method_name = "#{context}?"
|
|
23
|
+
|
|
24
|
+
resource.respond_to?(method_name) &&
|
|
25
|
+
resource.public_send(method_name)
|
|
26
|
+
end
|
|
27
|
+
|
|
15
28
|
private
|
|
16
29
|
|
|
17
30
|
def serialize_resource(resource)
|
|
@@ -19,7 +32,7 @@ module Userlist
|
|
|
19
32
|
|
|
20
33
|
serialized_resources << resource
|
|
21
34
|
|
|
22
|
-
return unless resource
|
|
35
|
+
return unless serialize?(resource)
|
|
23
36
|
|
|
24
37
|
serialized = {}
|
|
25
38
|
|
|
@@ -27,8 +40,8 @@ module Userlist
|
|
|
27
40
|
serialized[name] = resource.send(name)
|
|
28
41
|
end
|
|
29
42
|
|
|
30
|
-
resource.
|
|
31
|
-
next unless result =
|
|
43
|
+
resource.association_names.each do |name|
|
|
44
|
+
next unless result = serialize_association(resource.send(name))
|
|
32
45
|
|
|
33
46
|
serialized[name] = result
|
|
34
47
|
end
|
|
@@ -36,22 +49,22 @@ module Userlist
|
|
|
36
49
|
serialized
|
|
37
50
|
end
|
|
38
51
|
|
|
39
|
-
def
|
|
40
|
-
return unless
|
|
52
|
+
def serialize_association(association)
|
|
53
|
+
return unless association
|
|
41
54
|
|
|
42
|
-
case
|
|
55
|
+
case association
|
|
43
56
|
when Userlist::Push::ResourceCollection
|
|
44
|
-
serialize_collection(
|
|
57
|
+
serialize_collection(association)
|
|
45
58
|
when Userlist::Push::Resource
|
|
46
|
-
serialize_resource(
|
|
59
|
+
serialize_resource(association)
|
|
47
60
|
else
|
|
48
|
-
raise "Cannot serialize
|
|
61
|
+
raise "Cannot serialize association type: #{association.class}"
|
|
49
62
|
end
|
|
50
63
|
end
|
|
51
64
|
|
|
52
65
|
def serialize_collection(collection)
|
|
53
66
|
serialized = collection
|
|
54
|
-
.map(&method(:
|
|
67
|
+
.map(&method(:serialize_association))
|
|
55
68
|
.compact
|
|
56
69
|
.reject(&:empty?)
|
|
57
70
|
|
|
@@ -5,6 +5,8 @@ module Userlist
|
|
|
5
5
|
module Strategies
|
|
6
6
|
class ActiveJob
|
|
7
7
|
class Worker < ::ActiveJob::Base
|
|
8
|
+
retry_on Userlist::TimeoutError, Userlist::RequestError, wait: :polynomially_longer, attempts: 10
|
|
9
|
+
|
|
8
10
|
def perform(method, *args)
|
|
9
11
|
client = Userlist::Push::Client.new
|
|
10
12
|
client.public_send(method, *args)
|
|
@@ -18,14 +18,12 @@ module Userlist
|
|
|
18
18
|
logger.info 'Starting worker thread...'
|
|
19
19
|
|
|
20
20
|
loop do
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
logger.error "Failed to deliver payload: [#{e.class.name}] #{e.message}"
|
|
28
|
-
end
|
|
21
|
+
method, *args = *queue.pop
|
|
22
|
+
break if method == :stop
|
|
23
|
+
|
|
24
|
+
retryable.attempt { client.public_send(method, *args) }
|
|
25
|
+
rescue StandardError => e
|
|
26
|
+
logger.error "Failed to deliver payload: [#{e.class.name}] #{e.message}"
|
|
29
27
|
end
|
|
30
28
|
|
|
31
29
|
logger.info "Worker thread exited with #{queue.size} tasks still in the queue..."
|
|
@@ -46,11 +44,7 @@ module Userlist
|
|
|
46
44
|
end
|
|
47
45
|
|
|
48
46
|
def retryable
|
|
49
|
-
@retryable ||= Userlist::Retryable.new
|
|
50
|
-
status = response.code.to_i
|
|
51
|
-
|
|
52
|
-
status >= 500 || status == 429
|
|
53
|
-
end
|
|
47
|
+
@retryable ||= Userlist::Retryable.new
|
|
54
48
|
end
|
|
55
49
|
end
|
|
56
50
|
end
|
data/lib/userlist/push/user.rb
CHANGED
data/lib/userlist/push.rb
CHANGED
|
@@ -5,20 +5,21 @@ require 'userlist/push/resource'
|
|
|
5
5
|
require 'userlist/push/resource_collection'
|
|
6
6
|
require 'userlist/push/relation'
|
|
7
7
|
|
|
8
|
-
require 'userlist/push/operations/
|
|
8
|
+
require 'userlist/push/operations/push'
|
|
9
9
|
require 'userlist/push/operations/delete'
|
|
10
10
|
|
|
11
11
|
require 'userlist/push/user'
|
|
12
12
|
require 'userlist/push/company'
|
|
13
13
|
require 'userlist/push/relationship'
|
|
14
14
|
require 'userlist/push/event'
|
|
15
|
+
require 'userlist/push/message'
|
|
15
16
|
|
|
16
17
|
require 'userlist/push/serializer'
|
|
17
18
|
|
|
18
19
|
module Userlist
|
|
19
20
|
class Push
|
|
20
21
|
class << self
|
|
21
|
-
[:event, :track, :user, :identify, :company, :users, :events, :companies, :relationships].each do |method|
|
|
22
|
+
[:event, :track, :user, :identify, :company, :message, :users, :events, :companies, :relationships, :messages].each do |method|
|
|
22
23
|
define_method(method) { |*args| default_push_instance.send(method, *args) }
|
|
23
24
|
end
|
|
24
25
|
|
|
@@ -37,31 +38,39 @@ module Userlist
|
|
|
37
38
|
attr_reader :config, :strategy
|
|
38
39
|
|
|
39
40
|
def events
|
|
40
|
-
@events ||= Relation.new(self, Event, [Operations::
|
|
41
|
+
@events ||= Relation.new(self, Event, [Operations::Push])
|
|
41
42
|
end
|
|
42
43
|
|
|
43
44
|
def users
|
|
44
|
-
@users ||= Relation.new(self, User, [Operations::
|
|
45
|
+
@users ||= Relation.new(self, User, [Operations::Push, Operations::Delete])
|
|
45
46
|
end
|
|
46
47
|
|
|
47
48
|
def companies
|
|
48
|
-
@companies ||= Relation.new(self, Company, [Operations::
|
|
49
|
+
@companies ||= Relation.new(self, Company, [Operations::Push, Operations::Delete])
|
|
49
50
|
end
|
|
50
51
|
|
|
51
52
|
def relationships
|
|
52
|
-
@relationships ||= Relation.new(self, Relationship, [Operations::
|
|
53
|
+
@relationships ||= Relation.new(self, Relationship, [Operations::Push, Operations::Delete])
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def messages
|
|
57
|
+
@messages ||= Relation.new(self, Message, [Operations::Push])
|
|
53
58
|
end
|
|
54
59
|
|
|
55
60
|
def event(payload = {})
|
|
56
|
-
events.
|
|
61
|
+
events.push(payload)
|
|
57
62
|
end
|
|
58
63
|
|
|
59
64
|
def user(payload = {})
|
|
60
|
-
users.
|
|
65
|
+
users.push(payload)
|
|
61
66
|
end
|
|
62
67
|
|
|
63
68
|
def company(payload = {})
|
|
64
|
-
companies.
|
|
69
|
+
companies.push(payload)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def message(payload = {})
|
|
73
|
+
messages.push(payload)
|
|
65
74
|
end
|
|
66
75
|
|
|
67
76
|
alias track event
|
data/lib/userlist/retryable.rb
CHANGED
|
@@ -7,12 +7,24 @@ module Userlist
|
|
|
7
7
|
MULTIPLIER = 2
|
|
8
8
|
MAX_DELAY = 10_000
|
|
9
9
|
|
|
10
|
+
DEFAULT_RETRY_CHECK = lambda do |error|
|
|
11
|
+
case error
|
|
12
|
+
when Userlist::RequestError
|
|
13
|
+
status = error.status
|
|
14
|
+
status >= 500 || status == 429
|
|
15
|
+
when Userlist::TimeoutError
|
|
16
|
+
true
|
|
17
|
+
else
|
|
18
|
+
false
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
10
22
|
def initialize(retries: RETRIES, delay: DELAY, max_delay: MAX_DELAY, multiplier: MULTIPLIER, &retry_check)
|
|
11
23
|
@retries = retries
|
|
12
24
|
@delay = delay
|
|
13
25
|
@max_delay = max_delay
|
|
14
26
|
@multiplier = multiplier
|
|
15
|
-
@retry_check = retry_check
|
|
27
|
+
@retry_check = retry_check || DEFAULT_RETRY_CHECK
|
|
16
28
|
end
|
|
17
29
|
|
|
18
30
|
def retry?(value)
|
|
@@ -27,9 +39,9 @@ module Userlist
|
|
|
27
39
|
sleep(milliseconds / 1000.0)
|
|
28
40
|
end
|
|
29
41
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
42
|
+
return yield
|
|
43
|
+
rescue Userlist::Error => e
|
|
44
|
+
raise e unless retry?(e)
|
|
33
45
|
end
|
|
34
46
|
|
|
35
47
|
logger.debug 'Retries exhausted, giving up'
|
data/lib/userlist/version.rb
CHANGED
data/lib/userlist.rb
CHANGED
|
@@ -6,6 +6,7 @@ require 'userlist/logging'
|
|
|
6
6
|
require 'userlist/retryable'
|
|
7
7
|
require 'userlist/push'
|
|
8
8
|
require 'userlist/token'
|
|
9
|
+
require 'userlist/delivery_method'
|
|
9
10
|
|
|
10
11
|
module Userlist
|
|
11
12
|
class Error < StandardError; end
|
|
@@ -18,7 +19,7 @@ module Userlist
|
|
|
18
19
|
def initialize(key)
|
|
19
20
|
@key = key.to_sym
|
|
20
21
|
|
|
21
|
-
super
|
|
22
|
+
super(<<~MESSAGE)
|
|
22
23
|
Missing required configuration value for `#{key}`
|
|
23
24
|
|
|
24
25
|
Please set a value for `#{key}` using an environment variable:
|
|
@@ -34,6 +35,22 @@ module Userlist
|
|
|
34
35
|
end
|
|
35
36
|
end
|
|
36
37
|
|
|
38
|
+
class TimeoutError < Error; end
|
|
39
|
+
|
|
40
|
+
class RequestError < Error
|
|
41
|
+
attr_reader :response
|
|
42
|
+
|
|
43
|
+
def initialize(response)
|
|
44
|
+
super("Request failed with status #{response.code}: #{response.body}")
|
|
45
|
+
|
|
46
|
+
@response = response
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def status
|
|
50
|
+
@response.code.to_i
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
37
54
|
class << self
|
|
38
55
|
def config
|
|
39
56
|
@config ||= Userlist::Config.new
|
data/userlist.gemspec
CHANGED
|
@@ -9,7 +9,7 @@ Gem::Specification.new do |spec|
|
|
|
9
9
|
spec.email = ['benedikt@userlist.com']
|
|
10
10
|
|
|
11
11
|
spec.summary = 'Ruby wrapper for the Userlist API'
|
|
12
|
-
spec.homepage = 'http://github.com/
|
|
12
|
+
spec.homepage = 'http://github.com/userlist/userlist-ruby'
|
|
13
13
|
spec.license = 'MIT'
|
|
14
14
|
|
|
15
15
|
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
|
@@ -19,13 +19,7 @@ Gem::Specification.new do |spec|
|
|
|
19
19
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
20
20
|
spec.require_paths = ['lib']
|
|
21
21
|
|
|
22
|
-
spec.required_ruby_version = '>=
|
|
23
|
-
|
|
24
|
-
spec.add_development_dependency 'bundler', '>= 1.15'
|
|
25
|
-
spec.add_development_dependency 'jwt', '~> 2.2'
|
|
26
|
-
spec.add_development_dependency 'rake', '~> 13.0'
|
|
27
|
-
spec.add_development_dependency 'rspec', '~> 3.0'
|
|
28
|
-
spec.add_development_dependency 'webmock', '~> 3.18'
|
|
22
|
+
spec.required_ruby_version = '>= 3.0'
|
|
29
23
|
|
|
30
24
|
spec.metadata = { 'rubygems_mfa_required' => 'true' }
|
|
31
25
|
end
|
metadata
CHANGED
|
@@ -1,86 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: userlist
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 1.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Benedikt Deicke
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: exe
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
12
|
-
dependencies:
|
|
13
|
-
- !ruby/object:Gem::Dependency
|
|
14
|
-
name: bundler
|
|
15
|
-
requirement: !ruby/object:Gem::Requirement
|
|
16
|
-
requirements:
|
|
17
|
-
- - ">="
|
|
18
|
-
- !ruby/object:Gem::Version
|
|
19
|
-
version: '1.15'
|
|
20
|
-
type: :development
|
|
21
|
-
prerelease: false
|
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
-
requirements:
|
|
24
|
-
- - ">="
|
|
25
|
-
- !ruby/object:Gem::Version
|
|
26
|
-
version: '1.15'
|
|
27
|
-
- !ruby/object:Gem::Dependency
|
|
28
|
-
name: jwt
|
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
|
30
|
-
requirements:
|
|
31
|
-
- - "~>"
|
|
32
|
-
- !ruby/object:Gem::Version
|
|
33
|
-
version: '2.2'
|
|
34
|
-
type: :development
|
|
35
|
-
prerelease: false
|
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
-
requirements:
|
|
38
|
-
- - "~>"
|
|
39
|
-
- !ruby/object:Gem::Version
|
|
40
|
-
version: '2.2'
|
|
41
|
-
- !ruby/object:Gem::Dependency
|
|
42
|
-
name: rake
|
|
43
|
-
requirement: !ruby/object:Gem::Requirement
|
|
44
|
-
requirements:
|
|
45
|
-
- - "~>"
|
|
46
|
-
- !ruby/object:Gem::Version
|
|
47
|
-
version: '13.0'
|
|
48
|
-
type: :development
|
|
49
|
-
prerelease: false
|
|
50
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
-
requirements:
|
|
52
|
-
- - "~>"
|
|
53
|
-
- !ruby/object:Gem::Version
|
|
54
|
-
version: '13.0'
|
|
55
|
-
- !ruby/object:Gem::Dependency
|
|
56
|
-
name: rspec
|
|
57
|
-
requirement: !ruby/object:Gem::Requirement
|
|
58
|
-
requirements:
|
|
59
|
-
- - "~>"
|
|
60
|
-
- !ruby/object:Gem::Version
|
|
61
|
-
version: '3.0'
|
|
62
|
-
type: :development
|
|
63
|
-
prerelease: false
|
|
64
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
-
requirements:
|
|
66
|
-
- - "~>"
|
|
67
|
-
- !ruby/object:Gem::Version
|
|
68
|
-
version: '3.0'
|
|
69
|
-
- !ruby/object:Gem::Dependency
|
|
70
|
-
name: webmock
|
|
71
|
-
requirement: !ruby/object:Gem::Requirement
|
|
72
|
-
requirements:
|
|
73
|
-
- - "~>"
|
|
74
|
-
- !ruby/object:Gem::Version
|
|
75
|
-
version: '3.18'
|
|
76
|
-
type: :development
|
|
77
|
-
prerelease: false
|
|
78
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
-
requirements:
|
|
80
|
-
- - "~>"
|
|
81
|
-
- !ruby/object:Gem::Version
|
|
82
|
-
version: '3.18'
|
|
83
|
-
description:
|
|
10
|
+
date: 2025-10-17 00:00:00.000000000 Z
|
|
11
|
+
dependencies: []
|
|
84
12
|
email:
|
|
85
13
|
- benedikt@userlist.com
|
|
86
14
|
executables: []
|
|
@@ -102,13 +30,15 @@ files:
|
|
|
102
30
|
- bin/setup
|
|
103
31
|
- lib/userlist.rb
|
|
104
32
|
- lib/userlist/config.rb
|
|
33
|
+
- lib/userlist/delivery_method.rb
|
|
105
34
|
- lib/userlist/logging.rb
|
|
106
35
|
- lib/userlist/push.rb
|
|
107
36
|
- lib/userlist/push/client.rb
|
|
108
37
|
- lib/userlist/push/company.rb
|
|
109
38
|
- lib/userlist/push/event.rb
|
|
110
|
-
- lib/userlist/push/
|
|
39
|
+
- lib/userlist/push/message.rb
|
|
111
40
|
- lib/userlist/push/operations/delete.rb
|
|
41
|
+
- lib/userlist/push/operations/push.rb
|
|
112
42
|
- lib/userlist/push/relation.rb
|
|
113
43
|
- lib/userlist/push/relationship.rb
|
|
114
44
|
- lib/userlist/push/resource.rb
|
|
@@ -128,12 +58,11 @@ files:
|
|
|
128
58
|
- lib/userlist/token.rb
|
|
129
59
|
- lib/userlist/version.rb
|
|
130
60
|
- userlist.gemspec
|
|
131
|
-
homepage: http://github.com/
|
|
61
|
+
homepage: http://github.com/userlist/userlist-ruby
|
|
132
62
|
licenses:
|
|
133
63
|
- MIT
|
|
134
64
|
metadata:
|
|
135
65
|
rubygems_mfa_required: 'true'
|
|
136
|
-
post_install_message:
|
|
137
66
|
rdoc_options: []
|
|
138
67
|
require_paths:
|
|
139
68
|
- lib
|
|
@@ -141,15 +70,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
141
70
|
requirements:
|
|
142
71
|
- - ">="
|
|
143
72
|
- !ruby/object:Gem::Version
|
|
144
|
-
version: '
|
|
73
|
+
version: '3.0'
|
|
145
74
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
146
75
|
requirements:
|
|
147
76
|
- - ">="
|
|
148
77
|
- !ruby/object:Gem::Version
|
|
149
78
|
version: '0'
|
|
150
79
|
requirements: []
|
|
151
|
-
rubygems_version: 3.
|
|
152
|
-
signing_key:
|
|
80
|
+
rubygems_version: 3.6.2
|
|
153
81
|
specification_version: 4
|
|
154
82
|
summary: Ruby wrapper for the Userlist API
|
|
155
83
|
test_files: []
|