userlist 0.1.0 → 0.2.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 +5 -5
- data/.rubocop.yml +1 -1
- data/lib/userlist/config.rb +51 -11
- data/lib/userlist/logging.rb +11 -0
- data/lib/userlist/push/client.rb +9 -5
- data/lib/userlist/push/strategies/direct.rb +23 -0
- data/lib/userlist/push/strategies/null.rb +11 -0
- data/lib/userlist/push/strategies/threaded/worker.rb +51 -0
- data/lib/userlist/push/strategies/threaded.rb +28 -0
- data/lib/userlist/push/strategies.rb +17 -0
- data/lib/userlist/push.rb +62 -1
- data/lib/userlist/version.rb +1 -1
- data/lib/userlist.rb +18 -0
- data/userlist.gemspec +3 -2
- metadata +10 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: e36d328f443fa566c4e77f6d76bf7c3eb279677447d3451a56813868eef365c6
|
4
|
+
data.tar.gz: 42a788537faafae0cc66a8dcfd037c2ef7b2ca3ab78918922738e40e0b7c67b0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8ac7634b4f9a6059ee0c4c1c774be2d1b6d4ca2857eeceb4b4c6ef05ff7f070034780991cc196dc6f5ffe8f49f1f844a00f7ac614638f850d3e2e3a78ee644c8
|
7
|
+
data.tar.gz: 5a3154eeb7aa9368802961166c6fa89ab98c485d6c182d9a80d57d51edd0a09170da59237794d482fc5b1f470a19ce90f59ad5e297b864f6f633b028ea28fc3d
|
data/.rubocop.yml
CHANGED
data/lib/userlist/config.rb
CHANGED
@@ -1,39 +1,79 @@
|
|
1
1
|
module Userlist
|
2
2
|
class Config
|
3
3
|
DEFAULT_CONFIGURATION = {
|
4
|
-
push_key:
|
5
|
-
push_endpoint:
|
4
|
+
push_key: nil,
|
5
|
+
push_endpoint: 'https://push.userlist.io/',
|
6
|
+
push_strategy: :threaded,
|
7
|
+
log_level: :warn
|
6
8
|
}.freeze
|
7
9
|
|
8
10
|
def initialize(config_from_initialize = {})
|
9
|
-
@config =
|
11
|
+
@config = default_config
|
10
12
|
.merge(config_from_initialize)
|
11
13
|
.merge(config_from_environment)
|
12
14
|
end
|
13
15
|
|
14
|
-
|
16
|
+
def self.inherit(parent, config_from_arguments)
|
17
|
+
config = allocate
|
18
|
+
config.instance_variable_set(:@parent, parent)
|
19
|
+
config.instance_variable_set(:@config, config_from_arguments.to_hash)
|
20
|
+
config
|
21
|
+
end
|
22
|
+
|
23
|
+
def merge(other_config)
|
24
|
+
self.class.inherit(self, other_config)
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_hash
|
28
|
+
config
|
29
|
+
end
|
30
|
+
|
31
|
+
def to_h
|
32
|
+
parent ? parent.to_h.merge(config) : to_hash
|
33
|
+
end
|
34
|
+
|
35
|
+
def ==(other)
|
36
|
+
config == other.config && parent == other.parent
|
37
|
+
end
|
38
|
+
|
39
|
+
protected
|
15
40
|
|
16
|
-
attr_reader :config
|
41
|
+
attr_reader :config, :parent
|
42
|
+
|
43
|
+
def default_config
|
44
|
+
DEFAULT_CONFIGURATION
|
45
|
+
end
|
17
46
|
|
18
47
|
def config_from_environment
|
19
|
-
|
48
|
+
default_config.keys.each_with_object({}) do |key, config|
|
20
49
|
value = ENV["USERLIST_#{key.to_s.upcase}"]
|
21
50
|
config[key] = value if value
|
22
51
|
end
|
23
52
|
end
|
24
53
|
|
25
|
-
def
|
54
|
+
def key?(key)
|
55
|
+
config.key?(key) || parent && parent.key?(key)
|
56
|
+
end
|
57
|
+
|
58
|
+
def [](key)
|
59
|
+
config[key] || parent && parent[key]
|
60
|
+
end
|
61
|
+
|
62
|
+
def []=(key, value)
|
63
|
+
config[key] = value
|
64
|
+
end
|
65
|
+
|
66
|
+
def respond_to_missing?(name, include_private = false)
|
26
67
|
name = name.to_s.sub(/=$/, '')
|
27
|
-
|
68
|
+
key?(name.to_sym) || super
|
28
69
|
end
|
29
70
|
|
30
71
|
def method_missing(name, *args, &block)
|
31
|
-
name = name.to_s
|
32
|
-
|
33
72
|
if respond_to_missing?(name)
|
73
|
+
name = name.to_s
|
34
74
|
method = name.match?(/=$/) ? :[]= : :[]
|
35
75
|
name = name.sub(/=$/, '').to_sym
|
36
|
-
|
76
|
+
send(method, name, *args, &block)
|
37
77
|
else
|
38
78
|
super
|
39
79
|
end
|
data/lib/userlist/push/client.rb
CHANGED
@@ -4,10 +4,12 @@ require 'net/http'
|
|
4
4
|
require 'openssl'
|
5
5
|
|
6
6
|
module Userlist
|
7
|
-
|
7
|
+
class Push
|
8
8
|
class Client
|
9
|
-
|
10
|
-
|
9
|
+
include Userlist::Logging
|
10
|
+
|
11
|
+
def initialize(config = {})
|
12
|
+
@config = Userlist.config.merge(config)
|
11
13
|
end
|
12
14
|
|
13
15
|
def post(endpoint, payload = {})
|
@@ -31,13 +33,15 @@ module Userlist
|
|
31
33
|
end
|
32
34
|
end
|
33
35
|
|
34
|
-
def request(
|
35
|
-
request = Net::HTTP::Post.new(
|
36
|
+
def request(path, payload = {})
|
37
|
+
request = Net::HTTP::Post.new(path)
|
36
38
|
request['Accept'] = 'application/json'
|
37
39
|
request['Authorization'] = "Push #{token}"
|
38
40
|
request['Content-Type'] = 'application/json; charset=UTF-8'
|
39
41
|
request.body = JSON.dump(payload)
|
40
42
|
|
43
|
+
logger.debug "Sending #{request.method} to #{URI.join(endpoint, request.path)} with body #{request.body}"
|
44
|
+
|
41
45
|
http.request(request)
|
42
46
|
end
|
43
47
|
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Userlist
|
2
|
+
class Push
|
3
|
+
module Strategies
|
4
|
+
class Direct
|
5
|
+
def initialize(config = {})
|
6
|
+
@config = Userlist.config.merge(config)
|
7
|
+
end
|
8
|
+
|
9
|
+
def call(*args)
|
10
|
+
client.public_send(*args)
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
attr_reader :config
|
16
|
+
|
17
|
+
def client
|
18
|
+
@client ||= Userlist::Push::Client.new(config)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Userlist
|
2
|
+
class Push
|
3
|
+
module Strategies
|
4
|
+
class Threaded
|
5
|
+
class Worker
|
6
|
+
include Userlist::Logging
|
7
|
+
|
8
|
+
MAX_WORKER_TIMEOUT = 30
|
9
|
+
|
10
|
+
def initialize(queue, config = {})
|
11
|
+
@queue = queue
|
12
|
+
@config = Userlist.config.merge(config)
|
13
|
+
@thread = Thread.new { run }
|
14
|
+
@thread.abort_on_exception = true
|
15
|
+
end
|
16
|
+
|
17
|
+
def run
|
18
|
+
logger.info 'Starting worker thread...'
|
19
|
+
|
20
|
+
loop do
|
21
|
+
begin
|
22
|
+
method, url, payload = *queue.pop
|
23
|
+
break if method == :stop
|
24
|
+
|
25
|
+
client.public_send(method, url, payload)
|
26
|
+
rescue StandardError => exception
|
27
|
+
logger.error "Failed to deliver payload: [#{exception.class.name}] #{exception.message}"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
logger.info "Worker thread exited with #{queue.size} tasks still in the queue..."
|
32
|
+
end
|
33
|
+
|
34
|
+
def stop
|
35
|
+
logger.info 'Stopping worker thread...'
|
36
|
+
queue.push([:stop])
|
37
|
+
thread.join(MAX_WORKER_TIMEOUT)
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
attr_reader :queue, :config, :thread
|
43
|
+
|
44
|
+
def client
|
45
|
+
@client ||= Userlist::Push::Client.new(config)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'userlist/push/strategies/threaded/worker'
|
2
|
+
|
3
|
+
module Userlist
|
4
|
+
class Push
|
5
|
+
module Strategies
|
6
|
+
class Threaded
|
7
|
+
def initialize(config = {})
|
8
|
+
@queue = Queue.new
|
9
|
+
@worker = Worker.new(queue, config)
|
10
|
+
|
11
|
+
at_exit { stop_worker }
|
12
|
+
end
|
13
|
+
|
14
|
+
def call(*args)
|
15
|
+
queue.push(args)
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
attr_reader :queue, :worker
|
21
|
+
|
22
|
+
def stop_worker
|
23
|
+
worker && worker.stop
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'userlist/push/strategies/null'
|
2
|
+
require 'userlist/push/strategies/direct'
|
3
|
+
require 'userlist/push/strategies/threaded'
|
4
|
+
|
5
|
+
module Userlist
|
6
|
+
class Push
|
7
|
+
module Strategies
|
8
|
+
def self.strategy_for(strategy, config = {})
|
9
|
+
strategy = Userlist::Push::Strategies.const_get(strategy.to_s.capitalize) if strategy.is_a?(Symbol) || strategy.is_a?(String)
|
10
|
+
|
11
|
+
strategy = strategy.new(config) if strategy.respond_to?(:new)
|
12
|
+
|
13
|
+
strategy
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/userlist/push.rb
CHANGED
@@ -1,6 +1,67 @@
|
|
1
1
|
require 'userlist/push/client'
|
2
|
+
require 'userlist/push/strategies'
|
2
3
|
|
3
4
|
module Userlist
|
4
|
-
|
5
|
+
class Push
|
6
|
+
class << self
|
7
|
+
[:event, :track, :user, :identify, :company].each do |method|
|
8
|
+
define_method(method) { |*args| default_push_instance.send(method, *args) }
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def default_push_instance
|
14
|
+
@default_push_instance ||= new
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize(config = {})
|
19
|
+
@config = Userlist.config.merge(config)
|
20
|
+
@mutex = Mutex.new
|
21
|
+
end
|
22
|
+
|
23
|
+
def event(payload = {})
|
24
|
+
with_mutex do
|
25
|
+
raise ArgumentError, 'Missing required payload hash' unless payload
|
26
|
+
raise ArgumentError, 'Missing required parameter :name' unless payload[:name]
|
27
|
+
raise ArgumentError, 'Missing required parameter :user' unless payload[:user]
|
28
|
+
|
29
|
+
payload[:occured_at] ||= Time.now
|
30
|
+
|
31
|
+
strategy.call(:post, '/events', payload)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
alias track event
|
35
|
+
|
36
|
+
def user(payload = {})
|
37
|
+
with_mutex do
|
38
|
+
raise ArgumentError, 'Missing required payload hash' unless payload
|
39
|
+
raise ArgumentError, 'Missing required parameter :identifier' unless payload[:identifier]
|
40
|
+
|
41
|
+
strategy.call(:post, '/users', payload)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
alias identify user
|
45
|
+
|
46
|
+
def company(payload = {})
|
47
|
+
with_mutex do
|
48
|
+
raise ArgumentError, 'Missing required payload hash' unless payload
|
49
|
+
raise ArgumentError, 'Missing required parameter :identifier' unless payload[:identifier]
|
50
|
+
|
51
|
+
strategy.call(:post, '/companies', payload)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
attr_reader :config
|
58
|
+
|
59
|
+
def strategy
|
60
|
+
@strategy ||= Userlist::Push::Strategies.strategy_for(config.push_strategy, config)
|
61
|
+
end
|
62
|
+
|
63
|
+
def with_mutex(&block)
|
64
|
+
@mutex.synchronize(&block)
|
65
|
+
end
|
5
66
|
end
|
6
67
|
end
|
data/lib/userlist/version.rb
CHANGED
data/lib/userlist.rb
CHANGED
@@ -1,5 +1,8 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
1
3
|
require 'userlist/version'
|
2
4
|
require 'userlist/config'
|
5
|
+
require 'userlist/logging'
|
3
6
|
require 'userlist/push'
|
4
7
|
|
5
8
|
module Userlist
|
@@ -7,5 +10,20 @@ module Userlist
|
|
7
10
|
def config
|
8
11
|
@config ||= Userlist::Config.new
|
9
12
|
end
|
13
|
+
|
14
|
+
def logger
|
15
|
+
@logger ||= begin
|
16
|
+
logger = Logger.new(STDOUT)
|
17
|
+
logger.progname = 'userlist'
|
18
|
+
logger.level = config.log_level
|
19
|
+
logger
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def configure
|
24
|
+
yield config
|
25
|
+
end
|
26
|
+
|
27
|
+
attr_writer :logger
|
10
28
|
end
|
11
29
|
end
|
data/userlist.gemspec
CHANGED
@@ -1,5 +1,4 @@
|
|
1
|
-
|
2
|
-
lib = File.expand_path('../lib', __FILE__)
|
1
|
+
lib = File.expand_path('lib', __dir__)
|
3
2
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
3
|
require 'userlist/version'
|
5
4
|
|
@@ -20,6 +19,8 @@ Gem::Specification.new do |spec|
|
|
20
19
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
21
20
|
spec.require_paths = ['lib']
|
22
21
|
|
22
|
+
spec.required_ruby_version = '>= 2.1'
|
23
|
+
|
23
24
|
spec.add_development_dependency 'bundler', '~> 1.15'
|
24
25
|
spec.add_development_dependency 'rake', '~> 10.0'
|
25
26
|
spec.add_development_dependency 'rspec', '~> 3.0'
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: userlist
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Benedikt Deicke
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-
|
11
|
+
date: 2018-11-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -87,8 +87,14 @@ files:
|
|
87
87
|
- bin/setup
|
88
88
|
- lib/userlist.rb
|
89
89
|
- lib/userlist/config.rb
|
90
|
+
- lib/userlist/logging.rb
|
90
91
|
- lib/userlist/push.rb
|
91
92
|
- lib/userlist/push/client.rb
|
93
|
+
- lib/userlist/push/strategies.rb
|
94
|
+
- lib/userlist/push/strategies/direct.rb
|
95
|
+
- lib/userlist/push/strategies/null.rb
|
96
|
+
- lib/userlist/push/strategies/threaded.rb
|
97
|
+
- lib/userlist/push/strategies/threaded/worker.rb
|
92
98
|
- lib/userlist/version.rb
|
93
99
|
- userlist.gemspec
|
94
100
|
homepage: http://github.com/userlistio/userlist-ruby
|
@@ -103,7 +109,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
103
109
|
requirements:
|
104
110
|
- - ">="
|
105
111
|
- !ruby/object:Gem::Version
|
106
|
-
version: '
|
112
|
+
version: '2.1'
|
107
113
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
108
114
|
requirements:
|
109
115
|
- - ">="
|
@@ -111,7 +117,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
111
117
|
version: '0'
|
112
118
|
requirements: []
|
113
119
|
rubyforge_project:
|
114
|
-
rubygems_version: 2.
|
120
|
+
rubygems_version: 2.7.3
|
115
121
|
signing_key:
|
116
122
|
specification_version: 4
|
117
123
|
summary: Ruby wrapper for the Userlist.io API
|