talkable 0.0.0 → 0.9.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 +13 -5
- data/.gitignore +4 -2
- data/.rspec +1 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +126 -0
- data/LICENSE +22 -0
- data/README.md +144 -185
- data/Rakefile +17 -0
- data/lib/talkable.rb +43 -0
- data/lib/talkable/api.rb +13 -0
- data/lib/talkable/api/base.rb +98 -0
- data/lib/talkable/api/offer.rb +11 -0
- data/lib/talkable/api/origin.rb +29 -0
- data/lib/talkable/api/person.rb +17 -0
- data/lib/talkable/api/referral.rb +19 -0
- data/lib/talkable/api/reward.rb +19 -0
- data/lib/talkable/api/share.rb +19 -0
- data/lib/talkable/api/visitor.rb +13 -0
- data/lib/talkable/configuration.rb +40 -0
- data/lib/talkable/generators/install_generator.rb +81 -0
- data/lib/talkable/generators/templates/app/controllers/invite_controller.rb +8 -0
- data/lib/talkable/generators/templates/app/views/invite/show.html.erb +2 -0
- data/lib/talkable/generators/templates/app/views/invite/show.html.haml +2 -0
- data/lib/talkable/generators/templates/app/views/invite/show.html.slim +2 -0
- data/lib/talkable/generators/templates/app/views/shared/_talkable_offer.html.erb +4 -0
- data/lib/talkable/generators/templates/app/views/shared/_talkable_offer.html.haml +3 -0
- data/lib/talkable/generators/templates/app/views/shared/_talkable_offer.html.slim +3 -0
- data/lib/talkable/generators/templates/config/initializers/talkable.rb +13 -0
- data/lib/talkable/integration.rb +41 -0
- data/lib/talkable/middleware.rb +147 -0
- data/lib/talkable/railtie.rb +11 -0
- data/lib/talkable/resources.rb +3 -0
- data/lib/talkable/resources/offer.rb +49 -0
- data/lib/talkable/resources/origin.rb +22 -0
- data/lib/talkable/version.rb +3 -0
- data/solano.yml +20 -0
- data/talkable.gemspec +37 -0
- metadata +202 -9
data/Rakefile
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
require "bundler"
|
|
2
|
+
require "bundler/gem_tasks"
|
|
3
|
+
|
|
4
|
+
begin
|
|
5
|
+
Bundler.setup(:default, :development)
|
|
6
|
+
rescue Bundler::BundlerError => e
|
|
7
|
+
$stderr.puts e.message
|
|
8
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
|
9
|
+
exit e.status_code
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# RSpec tasks
|
|
13
|
+
require 'rspec/core'
|
|
14
|
+
require "rspec/core/rake_task"
|
|
15
|
+
RSpec::Core::RakeTask.new(:spec)
|
|
16
|
+
|
|
17
|
+
task :default => :spec
|
data/lib/talkable.rb
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
require 'talkable/version'
|
|
2
|
+
require 'talkable/configuration'
|
|
3
|
+
require 'talkable/api'
|
|
4
|
+
require 'talkable/resources'
|
|
5
|
+
require 'talkable/middleware'
|
|
6
|
+
require 'talkable/integration'
|
|
7
|
+
require 'talkable/railtie' if defined? ::Rails::Railtie
|
|
8
|
+
|
|
9
|
+
module Talkable
|
|
10
|
+
UUID = 'talkable_visitor_uuid'.freeze
|
|
11
|
+
|
|
12
|
+
class << self
|
|
13
|
+
def configure(config = nil)
|
|
14
|
+
configuration.apply config if config
|
|
15
|
+
yield(configuration) if block_given?
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def configuration
|
|
19
|
+
@configuration ||= Talkable::Configuration.new
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def visitor_uuid
|
|
23
|
+
Thread.current[UUID]
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def visitor_uuid=(uuid)
|
|
27
|
+
Thread.current[UUID] = uuid
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def with_uuid(uuid)
|
|
31
|
+
old_uuid, Talkable.visitor_uuid = Talkable.visitor_uuid, uuid
|
|
32
|
+
yield if block_given?
|
|
33
|
+
ensure
|
|
34
|
+
Talkable.visitor_uuid = old_uuid
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def find_or_generate_uuid
|
|
38
|
+
visitor_uuid || Talkable::API::Visitor.create[:uuid]
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
end
|
data/lib/talkable/api.rb
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
require 'talkable/api/base'
|
|
2
|
+
|
|
3
|
+
Dir[File.dirname(__FILE__) + '/api/*.rb'].each { |file| require file }
|
|
4
|
+
|
|
5
|
+
module Talkable
|
|
6
|
+
module API
|
|
7
|
+
VERSION = "v2".freeze
|
|
8
|
+
class BadRequest < StandardError
|
|
9
|
+
end
|
|
10
|
+
class NetworkError < StandardError
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
require 'net/https'
|
|
2
|
+
require 'json'
|
|
3
|
+
require 'furi'
|
|
4
|
+
|
|
5
|
+
module Talkable
|
|
6
|
+
module API
|
|
7
|
+
class Base
|
|
8
|
+
class << self
|
|
9
|
+
def get(path, params = {})
|
|
10
|
+
uri = request_uri(path, request_params(params))
|
|
11
|
+
request = Net::HTTP::Get.new(uri.request_uri)
|
|
12
|
+
perform_request(uri, request)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def post(path, params = {})
|
|
16
|
+
data_request(:post, path, params)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def put(path, params = {})
|
|
20
|
+
data_request(:put, path, params)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
protected
|
|
24
|
+
|
|
25
|
+
def data_request(method, path, params)
|
|
26
|
+
http_class = {post: Net::HTTP::Post, put: Net::HTTP::Put}[method.to_sym]
|
|
27
|
+
uri = request_uri(path)
|
|
28
|
+
request = http_class.new(uri.request_uri)
|
|
29
|
+
request.body = request_params(params).to_json
|
|
30
|
+
perform_request(uri, request)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def request_params(params = {})
|
|
34
|
+
params.merge({
|
|
35
|
+
api_key: Talkable.configuration.api_key,
|
|
36
|
+
site_slug: Talkable.configuration.site_slug,
|
|
37
|
+
})
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def request_uri(path, params = {})
|
|
41
|
+
URI(
|
|
42
|
+
Furi.update("#{Talkable.configuration.server}/api/#{Talkable::API::VERSION}#{path}",
|
|
43
|
+
query: params
|
|
44
|
+
)
|
|
45
|
+
)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def request_headers
|
|
49
|
+
{
|
|
50
|
+
'User-Agent' => "Talkable Gem/#{Talkable::VERSION}",
|
|
51
|
+
'Content-Type' => 'application/json',
|
|
52
|
+
'Accept' => 'application/json',
|
|
53
|
+
}
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def perform_request(uri, request)
|
|
57
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
58
|
+
http.use_ssl = uri.is_a?(URI::HTTPS)
|
|
59
|
+
|
|
60
|
+
request.initialize_http_header request_headers
|
|
61
|
+
process_response http.request(request)
|
|
62
|
+
rescue Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::ETIMEDOUT,
|
|
63
|
+
Errno::EHOSTUNREACH, Errno::ENETUNREACH, SocketError, Timeout::Error,
|
|
64
|
+
OpenSSL::SSL::SSLError, EOFError, Net::HTTPBadResponse => e
|
|
65
|
+
raise NetworkError.new(e.message)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def process_response(response)
|
|
69
|
+
case response
|
|
70
|
+
when Net::HTTPSuccess, Net::HTTPClientError
|
|
71
|
+
parse_response response.body
|
|
72
|
+
when Net::HTTPServerError
|
|
73
|
+
raise NetworkError.new("Server #{Talkable.configuration.server} is unavailable. Try again later")
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def parse_response(body)
|
|
78
|
+
raise_invalid_response if body.nil?
|
|
79
|
+
result = JSON.parse(body, symbolize_names: true)
|
|
80
|
+
raise_invalid_response unless result.is_a?(Hash)
|
|
81
|
+
|
|
82
|
+
if result[:ok]
|
|
83
|
+
result[:result]
|
|
84
|
+
else
|
|
85
|
+
raise BadRequest.new(result[:error_message])
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
rescue JSON::ParserError
|
|
89
|
+
raise_invalid_response
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def raise_invalid_response
|
|
93
|
+
raise BadRequest.new("Invalid response")
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
module Talkable
|
|
2
|
+
module API
|
|
3
|
+
class Origin < Base
|
|
4
|
+
AFFILIATE_MEMBER = "AffiliateMember".freeze
|
|
5
|
+
PURCHASE = "Purchase".freeze
|
|
6
|
+
EVENT = "Event".freeze
|
|
7
|
+
|
|
8
|
+
DEFAULT_TRAFFIC_SOURCE = 'talkable-gem'
|
|
9
|
+
|
|
10
|
+
class << self
|
|
11
|
+
def create(origin_type, params)
|
|
12
|
+
post '/origins', {
|
|
13
|
+
type: origin_type,
|
|
14
|
+
data: default_data.merge(params),
|
|
15
|
+
}
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
protected
|
|
19
|
+
|
|
20
|
+
def default_data
|
|
21
|
+
{
|
|
22
|
+
uuid: Talkable.visitor_uuid,
|
|
23
|
+
traffic_source: DEFAULT_TRAFFIC_SOURCE,
|
|
24
|
+
}
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module Talkable
|
|
2
|
+
module API
|
|
3
|
+
class Person < Base
|
|
4
|
+
class << self
|
|
5
|
+
def find(email_or_username)
|
|
6
|
+
get "/people/#{email_or_username}"
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def update(email_or_username, params)
|
|
10
|
+
put "/people/#{email_or_username}", {
|
|
11
|
+
data: params
|
|
12
|
+
}
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module Talkable
|
|
2
|
+
module API
|
|
3
|
+
class Referral < Base
|
|
4
|
+
APPROVED = 'approved'
|
|
5
|
+
VOIDED = 'voided'
|
|
6
|
+
UNBLOCKED = 'unblocked'
|
|
7
|
+
|
|
8
|
+
class << self
|
|
9
|
+
def update(origin_slug, status)
|
|
10
|
+
put "/origins/#{origin_slug}/referral", {
|
|
11
|
+
data: {
|
|
12
|
+
status: status
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module Talkable
|
|
2
|
+
module API
|
|
3
|
+
class Reward < Base
|
|
4
|
+
class << self
|
|
5
|
+
def find(params = {})
|
|
6
|
+
get "/rewards", default_params.merge(params)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
protected
|
|
10
|
+
|
|
11
|
+
def default_params
|
|
12
|
+
{
|
|
13
|
+
visitor_uuid: Talkable.visitor_uuid,
|
|
14
|
+
}
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module Talkable
|
|
2
|
+
module API
|
|
3
|
+
class Share < Base
|
|
4
|
+
CHANNEL_FACEBOOK = "facebook".freeze
|
|
5
|
+
CHANNEL_FACEBOOK_MESSAGE = "facebook_message".freeze
|
|
6
|
+
CHANNEL_TWITTER = "twitter".freeze
|
|
7
|
+
CHANNEL_SMS = "sms".freeze
|
|
8
|
+
CHANNEL_OTHER = "other".freeze
|
|
9
|
+
|
|
10
|
+
class << self
|
|
11
|
+
def create(short_url_code, channel)
|
|
12
|
+
post "/offers/#{short_url_code}/shares", {
|
|
13
|
+
channel: channel,
|
|
14
|
+
}
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
module Talkable
|
|
2
|
+
class Configuration
|
|
3
|
+
DEFAULT_SERVER = "https://www.talkable.com".freeze
|
|
4
|
+
|
|
5
|
+
attr_accessor :site_slug
|
|
6
|
+
attr_accessor :api_key
|
|
7
|
+
attr_accessor :server
|
|
8
|
+
attr_accessor :js_integration_library
|
|
9
|
+
|
|
10
|
+
class UnknownOptionError < StandardError
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def initialize
|
|
14
|
+
self.site_slug = ENV["TALKABLE_SITE_SLUG"]
|
|
15
|
+
self.api_key = ENV["TALKABLE_API_KEY"]
|
|
16
|
+
self.server = "https://www.talkable.com"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def apply(config)
|
|
20
|
+
config.each do |key, value|
|
|
21
|
+
if respond_to?("#{key}=")
|
|
22
|
+
public_send("#{key}=", value)
|
|
23
|
+
else
|
|
24
|
+
raise UnknownOptionError.new("There is no `#{key}` option")
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def js_integration_library
|
|
30
|
+
@js_integration_library || default_js_integration_library
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
protected
|
|
34
|
+
|
|
35
|
+
def default_js_integration_library
|
|
36
|
+
"//d2jjzw81hqbuqv.cloudfront.net/integration/clients/#{site_slug}.min.js"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
module Talkable
|
|
2
|
+
class InstallGenerator < Rails::Generators::Base
|
|
3
|
+
source_root File.expand_path("../templates", __FILE__)
|
|
4
|
+
class_option :haml, type: :boolean, default: false
|
|
5
|
+
class_option :slim, type: :boolean, default: false
|
|
6
|
+
|
|
7
|
+
def ask_config_values
|
|
8
|
+
@site_slug = ask("Your Talkable site slug:")
|
|
9
|
+
@api_key = ask("Your Talkable API Key:")
|
|
10
|
+
if yes?('Do you have a custom domain? [Y/n]')
|
|
11
|
+
@server = ask("Your custom domain [#{Talkable::Configuration::DEFAULT_SERVER}]:")
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def add_initializer
|
|
16
|
+
template "config/initializers/talkable.rb", "config/initializers/talkable.rb"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def inject_talkable_offer
|
|
20
|
+
inject_into_file "app/controllers/application_controller.rb", after: "protect_from_forgery with: :exception" do
|
|
21
|
+
<<-RUBY
|
|
22
|
+
|
|
23
|
+
before_action :load_talkable_offer
|
|
24
|
+
RUBY
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
inject_into_file "app/controllers/application_controller.rb", before: /^end/ do
|
|
28
|
+
<<-RUBY
|
|
29
|
+
protected
|
|
30
|
+
|
|
31
|
+
def load_talkable_offer
|
|
32
|
+
origin = Talkable.register_affiliate_member(campaign_tags: 'popup')
|
|
33
|
+
@offer ||= origin.offer if origin
|
|
34
|
+
end
|
|
35
|
+
RUBY
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
empty_directory "app/views/shared/"
|
|
39
|
+
|
|
40
|
+
if erb?
|
|
41
|
+
copy_file "app/views/shared/_talkable_offer.html.erb", "app/views/shared/_talkable_offer.html.erb"
|
|
42
|
+
inject_into_file "app/views/layouts/application.html.erb", before: "</body>" do
|
|
43
|
+
"<%= render 'shared/talkable_offer', offer: @offer %>\n"
|
|
44
|
+
end
|
|
45
|
+
else
|
|
46
|
+
ext = template_lang
|
|
47
|
+
|
|
48
|
+
copy_file "app/views/shared/_talkable_offer.html.#{ext}", "app/views/shared/_talkable_offer.html.#{ext}"
|
|
49
|
+
gsub_file "app/views/layouts/application.html.#{ext}", /^(\s*)\=\s*yield\s*$/ do |line|
|
|
50
|
+
paddings = line.match(/(\s*)\=/)[1]
|
|
51
|
+
"#{line}#{paddings}= render 'shared/talkable_offer', offer: @offer\n"
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def add_invite_controller
|
|
57
|
+
ext = template_lang
|
|
58
|
+
|
|
59
|
+
copy_file "app/controllers/invite_controller.rb", "app/controllers/invite_controller.rb"
|
|
60
|
+
copy_file "app/views/invite/show.html.#{ext}", "app/views/invite/show.html.#{ext}"
|
|
61
|
+
route "get '/invite' => 'invite#show'"
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
protected
|
|
65
|
+
|
|
66
|
+
def template_lang
|
|
67
|
+
@template_lang ||= if options[:haml]
|
|
68
|
+
'haml'
|
|
69
|
+
elsif options[:slim]
|
|
70
|
+
'slim'
|
|
71
|
+
else
|
|
72
|
+
Rails::Generators.options[:rails][:template_engine].to_s.downcase
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def erb?
|
|
77
|
+
template_lang == 'erb'
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
end
|
|
81
|
+
end
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
class InviteController < ApplicationController
|
|
2
|
+
skip_before_action :load_talkable_offer # skip default trigger widget integration
|
|
3
|
+
|
|
4
|
+
def show
|
|
5
|
+
origin = Talkable.register_affiliate_member(campaign_tags: 'invite')
|
|
6
|
+
@invite_offer = origin.offer if origin
|
|
7
|
+
end
|
|
8
|
+
end
|