mbuzz 0.6.2
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 +7 -0
- data/CHANGELOG.md +92 -0
- data/LICENSE.txt +21 -0
- data/README.md +173 -0
- data/Rakefile +8 -0
- data/lib/mbuzz/api.rb +102 -0
- data/lib/mbuzz/client/conversion_request.rb +85 -0
- data/lib/mbuzz/client/identify_request.rb +37 -0
- data/lib/mbuzz/client/session_request.rb +43 -0
- data/lib/mbuzz/client/track_request.rb +50 -0
- data/lib/mbuzz/client.rb +36 -0
- data/lib/mbuzz/configuration.rb +51 -0
- data/lib/mbuzz/controller_helpers.rb +34 -0
- data/lib/mbuzz/middleware/tracking.rb +157 -0
- data/lib/mbuzz/railtie.rb +13 -0
- data/lib/mbuzz/request_context.rb +40 -0
- data/lib/mbuzz/version.rb +5 -0
- data/lib/mbuzz/visitor/identifier.rb +13 -0
- data/lib/mbuzz.rb +178 -0
- data/lib/specs/old/SPECIFICATION.md +695 -0
- data/lib/specs/old/conversions.md +585 -0
- data/lib/specs/old/event_ids_response.md +346 -0
- data/lib/specs/old/v0.2.0_breaking_changes.md +519 -0
- data/lib/specs/old/v2.0.0_sessions_upgrade.md +265 -0
- data/lib/specs/v0.5.0_four_call_model.md +505 -0
- data/mbuzz-0.6.0.gem +0 -0
- data/sig/mbuzz.rbs +4 -0
- metadata +89 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 3c9557bce57bad2c91864640fcfff650794bb4de6d56afd3497eaa693247cc10
|
|
4
|
+
data.tar.gz: fef9164f73131538bc7e6df11e2b5fc802a967580b65b26426aa671e5d68cc26
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 2c0a78057cf989d08703c758f70b2c6711542c12994662ae47e5b9c67d1462aacd6e80d65b790bb287bb70e6ca03d61c60342eecb16f6c62059f4762359233ad
|
|
7
|
+
data.tar.gz: 8a6782fb3f84ce855438f64066becfb411f46f3db8ae68504dc5a96774b6a9ed4b0a04607da386888fca6683663c2ceddd1bd466b448df2fe82f4900fc03eee5
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [0.6.0] - 2025-12-05
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **Path filtering in middleware** - Automatically skip tracking for:
|
|
13
|
+
- Health check endpoints (`/up`, `/health`, `/healthz`, `/ping`)
|
|
14
|
+
- Asset paths (`/assets`, `/packs`, `/rails/active_storage`)
|
|
15
|
+
- WebSocket paths (`/cable`)
|
|
16
|
+
- API paths (`/api`)
|
|
17
|
+
- Static assets by extension (`.js`, `.css`, `.png`, `.woff2`, etc.)
|
|
18
|
+
- `skip_paths` configuration option - Add custom paths to skip
|
|
19
|
+
- `skip_extensions` configuration option - Add custom extensions to skip
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
|
|
23
|
+
- Health check requests no longer create sessions or consume API quota
|
|
24
|
+
- Static asset requests no longer pollute tracking data
|
|
25
|
+
|
|
26
|
+
### Usage
|
|
27
|
+
|
|
28
|
+
```ruby
|
|
29
|
+
Mbuzz.init(
|
|
30
|
+
api_key: "sk_live_...",
|
|
31
|
+
skip_paths: ["/admin", "/internal"], # Optional: additional paths to skip
|
|
32
|
+
skip_extensions: [".pdf", ".xml"] # Optional: additional extensions to skip
|
|
33
|
+
)
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## [0.5.0] - 2025-11-29
|
|
37
|
+
|
|
38
|
+
### Breaking Changes
|
|
39
|
+
|
|
40
|
+
- `Mbuzz.configure` replaced with `Mbuzz.init`
|
|
41
|
+
- `Mbuzz.track` renamed to `Mbuzz.event`
|
|
42
|
+
- `Mbuzz.alias` removed - merged into `Mbuzz.identify`
|
|
43
|
+
- `Mbuzz.identify` signature changed: positional user_id, keyword traits/visitor_id
|
|
44
|
+
|
|
45
|
+
### Added
|
|
46
|
+
|
|
47
|
+
- `Mbuzz.init(api_key:, ...)` - new configuration method
|
|
48
|
+
- `Mbuzz.event(event_type, **properties)` - cleaner event tracking
|
|
49
|
+
- Automatic visitor linking in `identify` when visitor_id available
|
|
50
|
+
- `enriched_properties` in RequestContext for URL/referrer auto-enrichment
|
|
51
|
+
|
|
52
|
+
### Removed
|
|
53
|
+
|
|
54
|
+
- `Mbuzz.configure` block syntax
|
|
55
|
+
- `Mbuzz.track` method
|
|
56
|
+
- `Mbuzz.alias` method
|
|
57
|
+
- `Mbuzz::Client::AliasRequest` class
|
|
58
|
+
|
|
59
|
+
## [0.2.0] - 2025-11-25
|
|
60
|
+
|
|
61
|
+
### BREAKING CHANGES
|
|
62
|
+
|
|
63
|
+
This release fixes critical bugs that prevented events from being tracked. You MUST update your code to use this version.
|
|
64
|
+
|
|
65
|
+
### Changed
|
|
66
|
+
|
|
67
|
+
- **BREAKING**: Renamed `event:` parameter to `event_type:` to match backend API
|
|
68
|
+
- Before: `Mbuzz.track(event: 'Signup', user_id: 1)`
|
|
69
|
+
- After: `Mbuzz.track(event_type: 'Signup', user_id: 1)`
|
|
70
|
+
- Migration: Search/replace `event:` → `event_type:` in all `Mbuzz.track()` calls
|
|
71
|
+
|
|
72
|
+
- **BREAKING**: Changed timestamp format from Unix epoch to ISO8601
|
|
73
|
+
- Before: Sent `1732550400` (integer)
|
|
74
|
+
- After: Sends `"2025-11-25T10:30:00Z"` (ISO8601 string)
|
|
75
|
+
- Migration: No action required - gem handles this automatically
|
|
76
|
+
|
|
77
|
+
### Fixed
|
|
78
|
+
|
|
79
|
+
- Events are now correctly formatted and accepted by backend
|
|
80
|
+
- Timestamps are now in UTC with ISO8601 format
|
|
81
|
+
|
|
82
|
+
## [0.1.0] - 2025-11-25
|
|
83
|
+
|
|
84
|
+
### Added
|
|
85
|
+
|
|
86
|
+
- Initial release
|
|
87
|
+
- Event tracking with `Mbuzz::Client.track()`
|
|
88
|
+
- User identification with `Mbuzz::Client.identify()`
|
|
89
|
+
- Visitor aliasing with `Mbuzz::Client.alias()`
|
|
90
|
+
- Automatic visitor and session management via middleware
|
|
91
|
+
- Rails integration via Railtie
|
|
92
|
+
- Controller helpers for convenient tracking
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 TODO: Write your name
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# mbuzz
|
|
2
|
+
|
|
3
|
+
Server-side multi-touch attribution for Ruby. Track customer journeys, attribute conversions, know which channels drive revenue.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Add to your Gemfile:
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
gem 'mbuzz'
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Then:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
bundle install
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Quick Start
|
|
20
|
+
|
|
21
|
+
### 1. Initialize
|
|
22
|
+
|
|
23
|
+
```ruby
|
|
24
|
+
# config/initializers/mbuzz.rb
|
|
25
|
+
Mbuzz.init(api_key: ENV['MBUZZ_API_KEY'])
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### 2. Track Events
|
|
29
|
+
|
|
30
|
+
Track steps in the customer journey:
|
|
31
|
+
|
|
32
|
+
```ruby
|
|
33
|
+
Mbuzz.event("page_view", url: request.url)
|
|
34
|
+
Mbuzz.event("add_to_cart", product_id: "SKU-123", price: 49.99)
|
|
35
|
+
Mbuzz.event("checkout_started", cart_total: 99.99)
|
|
36
|
+
|
|
37
|
+
# Group events into funnels for focused analysis
|
|
38
|
+
Mbuzz.event("signup_start", funnel: "signup", source: "homepage")
|
|
39
|
+
Mbuzz.event("signup_complete", funnel: "signup")
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### 3. Track Conversions
|
|
43
|
+
|
|
44
|
+
Record revenue-generating outcomes:
|
|
45
|
+
|
|
46
|
+
```ruby
|
|
47
|
+
Mbuzz.conversion("purchase",
|
|
48
|
+
revenue: 99.99,
|
|
49
|
+
funnel: "purchase", # Optional: group into funnel
|
|
50
|
+
order_id: order.id
|
|
51
|
+
)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### 4. Identify Users
|
|
55
|
+
|
|
56
|
+
Link visitors to known users (enables cross-device attribution):
|
|
57
|
+
|
|
58
|
+
```ruby
|
|
59
|
+
# On signup or login
|
|
60
|
+
Mbuzz.identify(current_user.id,
|
|
61
|
+
traits: {
|
|
62
|
+
email: current_user.email,
|
|
63
|
+
name: current_user.name
|
|
64
|
+
}
|
|
65
|
+
)
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Funnels
|
|
69
|
+
|
|
70
|
+
Group related events into **funnels** for focused conversion analysis in your dashboard.
|
|
71
|
+
|
|
72
|
+
```ruby
|
|
73
|
+
# Signup funnel
|
|
74
|
+
Mbuzz.event("pricing_view", funnel: "signup")
|
|
75
|
+
Mbuzz.event("signup_start", funnel: "signup")
|
|
76
|
+
Mbuzz.event("signup_complete", funnel: "signup")
|
|
77
|
+
|
|
78
|
+
# Purchase funnel
|
|
79
|
+
Mbuzz.event("add_to_cart", funnel: "purchase")
|
|
80
|
+
Mbuzz.event("checkout_started", funnel: "purchase")
|
|
81
|
+
Mbuzz.conversion("purchase", funnel: "purchase", revenue: 99.99)
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
**Why use funnels?**
|
|
85
|
+
- Separate signup flow from purchase flow
|
|
86
|
+
- Analyze each conversion path independently
|
|
87
|
+
- Filter dashboard to specific customer journeys
|
|
88
|
+
|
|
89
|
+
## Rails Integration
|
|
90
|
+
|
|
91
|
+
mbuzz auto-integrates with Rails via Railtie:
|
|
92
|
+
- Middleware for visitor and session cookie management
|
|
93
|
+
- Controller helpers for convenient access
|
|
94
|
+
|
|
95
|
+
### Controller Helpers
|
|
96
|
+
|
|
97
|
+
```ruby
|
|
98
|
+
class ApplicationController < ActionController::Base
|
|
99
|
+
# Access current IDs
|
|
100
|
+
mbuzz_visitor_id # Returns the visitor ID from cookie
|
|
101
|
+
mbuzz_user_id # Returns user_id from session["user_id"]
|
|
102
|
+
mbuzz_session_id # Returns the session ID from cookie
|
|
103
|
+
end
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
**Note:** For tracking and identification, prefer the main API which auto-enriches events with URL/referrer:
|
|
107
|
+
|
|
108
|
+
```ruby
|
|
109
|
+
# Recommended - use main API in controllers
|
|
110
|
+
Mbuzz.event("page_view", page: request.path)
|
|
111
|
+
Mbuzz.identify(current_user.id, traits: { email: current_user.email })
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Context Accessors
|
|
115
|
+
|
|
116
|
+
Access IDs from anywhere in your request cycle:
|
|
117
|
+
|
|
118
|
+
```ruby
|
|
119
|
+
Mbuzz.visitor_id # Current visitor ID (from cookie)
|
|
120
|
+
Mbuzz.user_id # Current user ID (from session["user_id"])
|
|
121
|
+
Mbuzz.session_id # Current session ID (from cookie)
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Rack / Sinatra Integration
|
|
125
|
+
|
|
126
|
+
For non-Rails apps, add the middleware manually:
|
|
127
|
+
|
|
128
|
+
```ruby
|
|
129
|
+
# config.ru or app.rb
|
|
130
|
+
require 'mbuzz'
|
|
131
|
+
|
|
132
|
+
Mbuzz.init(api_key: ENV['MBUZZ_API_KEY'])
|
|
133
|
+
|
|
134
|
+
use Mbuzz::Middleware::Tracking
|
|
135
|
+
run MyApp
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Configuration Options
|
|
139
|
+
|
|
140
|
+
```ruby
|
|
141
|
+
Mbuzz.init(
|
|
142
|
+
api_key: "sk_live_...", # Required - from mbuzz.co dashboard
|
|
143
|
+
api_url: "https://mbuzz.co/api/v1", # Optional - API endpoint
|
|
144
|
+
debug: false # Optional - enable debug logging
|
|
145
|
+
)
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## The 4-Call Model
|
|
149
|
+
|
|
150
|
+
| Method | When to Use |
|
|
151
|
+
|--------|-------------|
|
|
152
|
+
| `init` | Once on app boot |
|
|
153
|
+
| `event` | User interactions, funnel steps |
|
|
154
|
+
| `conversion` | Purchases, signups, any revenue event |
|
|
155
|
+
| `identify` | Login, signup, when you know the user |
|
|
156
|
+
|
|
157
|
+
## Error Handling
|
|
158
|
+
|
|
159
|
+
mbuzz never raises exceptions. All methods return `false` on failure and log errors in debug mode.
|
|
160
|
+
|
|
161
|
+
## Requirements
|
|
162
|
+
|
|
163
|
+
- Ruby 3.0+
|
|
164
|
+
- Rails 6.0+ (for automatic integration) or any Rack app
|
|
165
|
+
|
|
166
|
+
## Links
|
|
167
|
+
|
|
168
|
+
- [Documentation](https://mbuzz.co/docs)
|
|
169
|
+
- [Dashboard](https://mbuzz.co/dashboard)
|
|
170
|
+
|
|
171
|
+
## License
|
|
172
|
+
|
|
173
|
+
MIT License
|
data/Rakefile
ADDED
data/lib/mbuzz/api.rb
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "net/http"
|
|
4
|
+
require "json"
|
|
5
|
+
require "uri"
|
|
6
|
+
require "openssl"
|
|
7
|
+
|
|
8
|
+
module Mbuzz
|
|
9
|
+
class Api
|
|
10
|
+
def self.post(path, payload)
|
|
11
|
+
return false unless enabled_and_configured?
|
|
12
|
+
|
|
13
|
+
response = http_client(path).request(build_request(path, payload))
|
|
14
|
+
success?(response)
|
|
15
|
+
rescue ConfigurationError, Net::ReadTimeout, Net::OpenTimeout, Net::HTTPError => e
|
|
16
|
+
log_error("#{e.class}: #{e.message}")
|
|
17
|
+
false
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def self.post_with_response(path, payload)
|
|
21
|
+
return nil unless enabled_and_configured?
|
|
22
|
+
|
|
23
|
+
response = http_client(path).request(build_request(path, payload))
|
|
24
|
+
return nil unless success?(response)
|
|
25
|
+
|
|
26
|
+
JSON.parse(response.body)
|
|
27
|
+
rescue ConfigurationError, Net::ReadTimeout, Net::OpenTimeout, Net::HTTPError, JSON::ParserError => e
|
|
28
|
+
log_error("#{e.class}: #{e.message}")
|
|
29
|
+
nil
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def self.enabled_and_configured?
|
|
33
|
+
return false unless config.enabled
|
|
34
|
+
config.validate!
|
|
35
|
+
true
|
|
36
|
+
rescue ConfigurationError => e
|
|
37
|
+
log_error(e.message)
|
|
38
|
+
false
|
|
39
|
+
end
|
|
40
|
+
private_class_method :enabled_and_configured?
|
|
41
|
+
|
|
42
|
+
def self.uri(path)
|
|
43
|
+
@uri_cache ||= {}
|
|
44
|
+
@uri_cache[path] ||= begin
|
|
45
|
+
base = config.api_url.chomp("/")
|
|
46
|
+
endpoint = path.start_with?("/") ? path : "/#{path}"
|
|
47
|
+
URI.parse("#{base}#{endpoint}")
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
private_class_method :uri
|
|
51
|
+
|
|
52
|
+
def self.http_client(path)
|
|
53
|
+
Net::HTTP.new(uri(path).host, uri(path).port).tap do |http|
|
|
54
|
+
if uri(path).scheme == "https"
|
|
55
|
+
http.use_ssl = true
|
|
56
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
|
57
|
+
http.cert_store = ssl_cert_store
|
|
58
|
+
end
|
|
59
|
+
http.open_timeout = config.timeout
|
|
60
|
+
http.read_timeout = config.timeout
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
private_class_method :http_client
|
|
64
|
+
|
|
65
|
+
def self.ssl_cert_store
|
|
66
|
+
@ssl_cert_store ||= OpenSSL::X509::Store.new.tap do |store|
|
|
67
|
+
store.set_default_paths
|
|
68
|
+
# Don't set any CRL flags - Let's Encrypt uses OCSP, not CRL
|
|
69
|
+
store.flags = 0
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
private_class_method :ssl_cert_store
|
|
73
|
+
|
|
74
|
+
def self.build_request(path, payload)
|
|
75
|
+
Net::HTTP::Post.new(uri(path).path).tap do |request|
|
|
76
|
+
request["Authorization"] = "Bearer #{config.api_key}"
|
|
77
|
+
request["Content-Type"] = "application/json"
|
|
78
|
+
request["User-Agent"] = "mbuzz-ruby/#{VERSION}"
|
|
79
|
+
request.body = JSON.generate(payload)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
private_class_method :build_request
|
|
83
|
+
|
|
84
|
+
def self.success?(response)
|
|
85
|
+
return true if response.code.to_i.between?(200, 299)
|
|
86
|
+
|
|
87
|
+
log_error("API #{response.code}: #{response.body}")
|
|
88
|
+
false
|
|
89
|
+
end
|
|
90
|
+
private_class_method :success?
|
|
91
|
+
|
|
92
|
+
def self.log_error(message)
|
|
93
|
+
warn "[mbuzz] #{message}" if config.debug
|
|
94
|
+
end
|
|
95
|
+
private_class_method :log_error
|
|
96
|
+
|
|
97
|
+
def self.config
|
|
98
|
+
Mbuzz.config
|
|
99
|
+
end
|
|
100
|
+
private_class_method :config
|
|
101
|
+
end
|
|
102
|
+
end
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Mbuzz
|
|
4
|
+
class Client
|
|
5
|
+
class ConversionRequest
|
|
6
|
+
def initialize(event_id:, visitor_id:, user_id:, conversion_type:, revenue:, currency:, is_acquisition:, inherit_acquisition:, properties:)
|
|
7
|
+
@event_id = event_id
|
|
8
|
+
@visitor_id = visitor_id
|
|
9
|
+
@user_id = user_id
|
|
10
|
+
@conversion_type = conversion_type
|
|
11
|
+
@revenue = revenue
|
|
12
|
+
@currency = currency
|
|
13
|
+
@is_acquisition = is_acquisition
|
|
14
|
+
@inherit_acquisition = inherit_acquisition
|
|
15
|
+
@properties = properties
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def call
|
|
19
|
+
return false unless valid?
|
|
20
|
+
|
|
21
|
+
{ success: true, conversion_id: conversion_id, attribution: attribution }
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def valid?
|
|
27
|
+
has_identifier? && present?(@conversion_type) && hash?(@properties) && conversion_id
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def has_identifier?
|
|
31
|
+
present?(@event_id) || present?(@visitor_id) || present?(@user_id)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def conversion_id
|
|
35
|
+
@conversion_id ||= response&.dig("conversion", "id")
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def attribution
|
|
39
|
+
response&.dig("attribution")
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def response
|
|
43
|
+
@response ||= Api.post_with_response(CONVERSIONS_PATH, payload)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def payload
|
|
47
|
+
{ conversion: conversion_payload }
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def conversion_payload
|
|
51
|
+
base_payload
|
|
52
|
+
.merge(optional_identifiers)
|
|
53
|
+
.merge(optional_acquisition_fields)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def base_payload
|
|
57
|
+
{
|
|
58
|
+
conversion_type: @conversion_type,
|
|
59
|
+
currency: @currency,
|
|
60
|
+
properties: @properties,
|
|
61
|
+
timestamp: Time.now.utc.iso8601
|
|
62
|
+
}
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def optional_identifiers
|
|
66
|
+
{}.tap do |h|
|
|
67
|
+
h[:event_id] = @event_id if @event_id
|
|
68
|
+
h[:visitor_id] = @visitor_id if @visitor_id
|
|
69
|
+
h[:user_id] = @user_id if @user_id
|
|
70
|
+
h[:revenue] = @revenue if @revenue
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def optional_acquisition_fields
|
|
75
|
+
{}.tap do |h|
|
|
76
|
+
h[:is_acquisition] = @is_acquisition if @is_acquisition
|
|
77
|
+
h[:inherit_acquisition] = @inherit_acquisition if @inherit_acquisition
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def present?(value) = value && !value.to_s.strip.empty?
|
|
82
|
+
def hash?(value) = value.is_a?(Hash)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Mbuzz
|
|
4
|
+
class Client
|
|
5
|
+
class IdentifyRequest
|
|
6
|
+
def initialize(user_id, visitor_id, traits)
|
|
7
|
+
@user_id = user_id
|
|
8
|
+
@visitor_id = visitor_id
|
|
9
|
+
@traits = traits
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def call
|
|
13
|
+
return false unless valid?
|
|
14
|
+
|
|
15
|
+
Api.post(IDENTIFY_PATH, payload)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def valid?
|
|
21
|
+
id?(@user_id) && hash?(@traits)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def payload
|
|
25
|
+
{
|
|
26
|
+
user_id: @user_id,
|
|
27
|
+
visitor_id: @visitor_id,
|
|
28
|
+
traits: @traits,
|
|
29
|
+
timestamp: Time.now.utc.iso8601
|
|
30
|
+
}.compact
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def id?(value) = value.is_a?(String) || value.is_a?(Numeric)
|
|
34
|
+
def hash?(value) = value.is_a?(Hash)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Mbuzz
|
|
4
|
+
class Client
|
|
5
|
+
class SessionRequest
|
|
6
|
+
def initialize(visitor_id, session_id, url, referrer, started_at)
|
|
7
|
+
@visitor_id = visitor_id
|
|
8
|
+
@session_id = session_id
|
|
9
|
+
@url = url
|
|
10
|
+
@referrer = referrer
|
|
11
|
+
@started_at = started_at || Time.now.utc.iso8601
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def call
|
|
15
|
+
return false unless valid?
|
|
16
|
+
|
|
17
|
+
Api.post(SESSIONS_PATH, payload)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
attr_reader :visitor_id, :session_id, :url, :referrer, :started_at
|
|
23
|
+
|
|
24
|
+
def valid?
|
|
25
|
+
present?(visitor_id) && present?(session_id) && present?(url)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def payload
|
|
29
|
+
{
|
|
30
|
+
session: {
|
|
31
|
+
visitor_id: visitor_id,
|
|
32
|
+
session_id: session_id,
|
|
33
|
+
url: url,
|
|
34
|
+
referrer: referrer,
|
|
35
|
+
started_at: started_at
|
|
36
|
+
}.compact
|
|
37
|
+
}
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def present?(value) = value && !value.to_s.strip.empty?
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Mbuzz
|
|
4
|
+
class Client
|
|
5
|
+
class TrackRequest
|
|
6
|
+
def initialize(user_id, visitor_id, session_id, event_type, properties)
|
|
7
|
+
@user_id = user_id
|
|
8
|
+
@visitor_id = visitor_id
|
|
9
|
+
@session_id = session_id
|
|
10
|
+
@event_type = event_type
|
|
11
|
+
@properties = properties
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def call
|
|
15
|
+
return false unless valid?
|
|
16
|
+
|
|
17
|
+
{ success: true, event_id: event["id"], event_type: event["event_type"],
|
|
18
|
+
visitor_id: event["visitor_id"], session_id: event["session_id"] }
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def valid?
|
|
24
|
+
present?(@event_type) && hash?(@properties) && (@user_id || @visitor_id) && event
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def event
|
|
28
|
+
@event ||= response&.dig("events", 0)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def response
|
|
32
|
+
@response ||= Api.post_with_response(EVENTS_PATH, { events: [payload] })
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def payload
|
|
36
|
+
{
|
|
37
|
+
user_id: @user_id,
|
|
38
|
+
visitor_id: @visitor_id,
|
|
39
|
+
session_id: @session_id,
|
|
40
|
+
event_type: @event_type,
|
|
41
|
+
properties: @properties,
|
|
42
|
+
timestamp: Time.now.utc.iso8601
|
|
43
|
+
}.compact
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def present?(value) = value && !value.to_s.strip.empty?
|
|
47
|
+
def hash?(value) = value.is_a?(Hash)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
data/lib/mbuzz/client.rb
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "client/track_request"
|
|
4
|
+
require_relative "client/identify_request"
|
|
5
|
+
require_relative "client/conversion_request"
|
|
6
|
+
require_relative "client/session_request"
|
|
7
|
+
|
|
8
|
+
module Mbuzz
|
|
9
|
+
class Client
|
|
10
|
+
def self.track(user_id: nil, visitor_id: nil, session_id: nil, event_type:, properties: {})
|
|
11
|
+
TrackRequest.new(user_id, visitor_id, session_id, event_type, properties).call
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def self.identify(user_id:, visitor_id: nil, traits: {})
|
|
15
|
+
IdentifyRequest.new(user_id, visitor_id, traits).call
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def self.conversion(event_id: nil, visitor_id: nil, user_id: nil, conversion_type:, revenue: nil, currency: "USD", is_acquisition: false, inherit_acquisition: false, properties: {})
|
|
19
|
+
ConversionRequest.new(
|
|
20
|
+
event_id: event_id,
|
|
21
|
+
visitor_id: visitor_id,
|
|
22
|
+
user_id: user_id,
|
|
23
|
+
conversion_type: conversion_type,
|
|
24
|
+
revenue: revenue,
|
|
25
|
+
currency: currency,
|
|
26
|
+
is_acquisition: is_acquisition,
|
|
27
|
+
inherit_acquisition: inherit_acquisition,
|
|
28
|
+
properties: properties
|
|
29
|
+
).call
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def self.session(visitor_id:, session_id:, url:, referrer: nil, started_at: nil)
|
|
33
|
+
SessionRequest.new(visitor_id, session_id, url, referrer, started_at).call
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|