mbuzz 0.6.7 → 0.7.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 28da4faa3b1b26c01e8a1762b5c9f24352d656304eb7802c71e0e00057171702
4
- data.tar.gz: 126f8ddaf74ffb112c0e6e09efa63019563c37edf551b4493676f2977d5ce025
3
+ metadata.gz: 0fa7c025330829c88ab9a75a22ca286d14adc1df7872295c5519df4c89536db9
4
+ data.tar.gz: 62d6675e5ffe9504e2262f84551c286366acc2ad42b6ca6d224367902d9ec3fb
5
5
  SHA512:
6
- metadata.gz: 574d624c96d1a50c87ca31d4f3528676826cd6f0d5b37956858825d0338fd60468d87ff72a0791de167a5f18f5644148ef45f74f52ae9c2945765ad7d5c3c9ff
7
- data.tar.gz: 7139e7f2911fee98640f181f868213f739e4104fd76708082fdc26d27ac679fd56859573a034d9883ba27d069c4569159cabcadcc1900c15c7140f1a5f0da3f8
6
+ metadata.gz: eb90968b0d4c17bdc7c5019fa0058c257233bc00e1921f0b70a3dbf885fd8aecd1f9d80ab125d31ab535be5e55a44ecc2e0d46e9cb9c51fef4b9305634241b2f
7
+ data.tar.gz: cef7cbbc7817a6d7c4eca635594ac9e3b9661d81b33cc7d956376cb9fd763a6d216651e6ec499146e31dca042f024216f3c85c08c6d83f4f31dfb1fff64e8b56
data/CHANGELOG.md CHANGED
@@ -5,6 +5,54 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.7.0] - 2026-01-09
9
+
10
+ ### Breaking Changes
11
+
12
+ - **Session cookie removed** - SDK no longer sets or reads `_mbuzz_sid` cookie
13
+ - **Session ID generation removed** - Server handles all session resolution
14
+ - **`Mbuzz.session_id` removed** - Use server-side session resolution instead
15
+ - **`Mbuzz::Client.session()` removed** - Sessions are created server-side
16
+ - **`session_id` parameter removed from `Client.track()`** - Not needed with server-side resolution
17
+
18
+ ### Added
19
+
20
+ - **Cross-device identity resolution** - New `identifier` parameter for linking sessions across devices
21
+ - `Mbuzz.event("page_view", identifier: { email: "user@example.com" })`
22
+ - `Mbuzz.conversion("purchase", identifier: { email: "user@example.com" })`
23
+ - **Conversion fingerprint fallback** - `ip` and `user_agent` parameters on `Client.conversion()`
24
+ - When visitor_id is not found, server can find visitor via recent session with same fingerprint
25
+
26
+ ### Changed
27
+
28
+ - **Simplified middleware** - Only manages visitor cookie (`_mbuzz_vid`), no session handling
29
+ - **Server-side session resolution** - All session creation and resolution happens on the API server
30
+ - Enables true 30-minute sliding windows (vs fixed time buckets)
31
+ - Eliminates duplicate visitor problem from concurrent Turbo/Hotwire requests
32
+ - Better cross-device tracking with identity resolution
33
+
34
+ ### Migration Guide
35
+
36
+ 1. Remove any code that reads `Mbuzz.session_id` or `_mbuzz_sid` cookie
37
+ 2. Remove any calls to `Mbuzz::Client.session()`
38
+ 3. Ensure `ip` and `user_agent` are passed to track/conversion calls (handled automatically if using middleware)
39
+ 4. Optionally add `identifier` parameter for cross-device tracking
40
+
41
+ ## [0.6.8] - 2025-12-30
42
+
43
+ ### Added
44
+
45
+ - **Server-side session resolution support** - SDK now forwards `ip` and `user_agent` to the API for server-side session identification
46
+ - `ip` and `user_agent` parameters on `Mbuzz::Client.track()`
47
+ - `RequestContext#ip` method with proxy header support (X-Forwarded-For, X-Real-IP)
48
+ - `Mbuzz.event` automatically extracts and forwards ip/user_agent from request context
49
+
50
+ ### Technical Details
51
+
52
+ - IP extraction priority: `X-Forwarded-For` (first IP) > `X-Real-IP` > direct IP
53
+ - Enables accurate session tracking without client-side cookies
54
+ - Backwards compatible - ip/user_agent are optional parameters
55
+
8
56
  ## [0.6.0] - 2025-12-05
9
57
 
10
58
  ### Added
@@ -3,7 +3,7 @@
3
3
  module Mbuzz
4
4
  class Client
5
5
  class ConversionRequest
6
- def initialize(event_id:, visitor_id:, user_id:, conversion_type:, revenue:, currency:, is_acquisition:, inherit_acquisition:, properties:)
6
+ def initialize(event_id:, visitor_id:, user_id:, conversion_type:, revenue:, currency:, is_acquisition:, inherit_acquisition:, properties:, ip: nil, user_agent: nil, identifier: nil)
7
7
  @event_id = event_id
8
8
  @visitor_id = visitor_id
9
9
  @user_id = user_id
@@ -13,6 +13,9 @@ module Mbuzz
13
13
  @is_acquisition = is_acquisition
14
14
  @inherit_acquisition = inherit_acquisition
15
15
  @properties = properties
16
+ @ip = ip
17
+ @user_agent = user_agent
18
+ @identifier = identifier
16
19
  end
17
20
 
18
21
  def call
@@ -51,6 +54,7 @@ module Mbuzz
51
54
  base_payload
52
55
  .merge(optional_identifiers)
53
56
  .merge(optional_acquisition_fields)
57
+ .merge(fingerprint_fields)
54
58
  end
55
59
 
56
60
  def base_payload
@@ -78,6 +82,14 @@ module Mbuzz
78
82
  end
79
83
  end
80
84
 
85
+ def fingerprint_fields
86
+ {}.tap do |h|
87
+ h[:ip] = @ip if @ip
88
+ h[:user_agent] = @user_agent if @user_agent
89
+ h[:identifier] = @identifier if @identifier
90
+ end
91
+ end
92
+
81
93
  def present?(value) = value && !value.to_s.strip.empty?
82
94
  def hash?(value) = value.is_a?(Hash)
83
95
  end
@@ -3,12 +3,14 @@
3
3
  module Mbuzz
4
4
  class Client
5
5
  class TrackRequest
6
- def initialize(user_id, visitor_id, session_id, event_type, properties)
6
+ def initialize(user_id, visitor_id, event_type, properties, ip = nil, user_agent = nil, identifier = nil)
7
7
  @user_id = user_id
8
8
  @visitor_id = visitor_id
9
- @session_id = session_id
10
9
  @event_type = event_type
11
10
  @properties = properties
11
+ @ip = ip
12
+ @user_agent = user_agent
13
+ @identifier = identifier
12
14
  end
13
15
 
14
16
  def call
@@ -36,9 +38,11 @@ module Mbuzz
36
38
  {
37
39
  user_id: @user_id,
38
40
  visitor_id: @visitor_id,
39
- session_id: @session_id,
40
41
  event_type: @event_type,
41
42
  properties: @properties,
43
+ ip: @ip,
44
+ user_agent: @user_agent,
45
+ identifier: @identifier,
42
46
  timestamp: Time.now.utc.iso8601
43
47
  }.compact
44
48
  end
data/lib/mbuzz/client.rb CHANGED
@@ -3,19 +3,18 @@
3
3
  require_relative "client/track_request"
4
4
  require_relative "client/identify_request"
5
5
  require_relative "client/conversion_request"
6
- require_relative "client/session_request"
7
6
 
8
7
  module Mbuzz
9
8
  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
9
+ def self.track(user_id: nil, visitor_id: nil, event_type:, properties: {}, ip: nil, user_agent: nil, identifier: nil)
10
+ TrackRequest.new(user_id, visitor_id, event_type, properties, ip, user_agent, identifier).call
12
11
  end
13
12
 
14
13
  def self.identify(user_id:, visitor_id: nil, traits: {})
15
14
  IdentifyRequest.new(user_id, visitor_id, traits).call
16
15
  end
17
16
 
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: {})
17
+ 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: {}, ip: nil, user_agent: nil, identifier: nil)
19
18
  ConversionRequest.new(
20
19
  event_id: event_id,
21
20
  visitor_id: visitor_id,
@@ -25,12 +24,11 @@ module Mbuzz
25
24
  currency: currency,
26
25
  is_acquisition: is_acquisition,
27
26
  inherit_acquisition: inherit_acquisition,
28
- properties: properties
27
+ properties: properties,
28
+ ip: ip,
29
+ user_agent: user_agent,
30
+ identifier: identifier
29
31
  ).call
30
32
  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
33
  end
36
34
  end
@@ -26,9 +26,5 @@ module Mbuzz
26
26
  def mbuzz_visitor_id
27
27
  request.env[ENV_VISITOR_ID_KEY]
28
28
  end
29
-
30
- def mbuzz_session_id
31
- request.env[ENV_SESSION_ID_KEY]
32
- end
33
29
  end
34
30
  end
@@ -17,13 +17,10 @@ module Mbuzz
17
17
 
18
18
  env[ENV_VISITOR_ID_KEY] = context[:visitor_id]
19
19
  env[ENV_USER_ID_KEY] = context[:user_id]
20
- env[ENV_SESSION_ID_KEY] = context[:session_id]
21
20
 
22
21
  RequestContext.with_context(request: request) do
23
- create_session_if_new(context, request)
24
-
25
22
  status, headers, body = @app.call(env)
26
- set_cookies(headers, context, request)
23
+ set_visitor_cookie(headers, context, request)
27
24
  [status, headers, body]
28
25
  end
29
26
  end
@@ -51,9 +48,7 @@ module Mbuzz
51
48
  def build_request_context(request)
52
49
  {
53
50
  visitor_id: resolve_visitor_id(request),
54
- session_id: resolve_session_id(request),
55
- user_id: user_id_from_session(request),
56
- new_session: new_session?(request)
51
+ user_id: user_id_from_session(request)
57
52
  }.freeze
58
53
  end
59
54
 
@@ -61,91 +56,15 @@ module Mbuzz
61
56
  visitor_id_from_cookie(request) || Visitor::Identifier.generate
62
57
  end
63
58
 
64
- def resolve_session_id(request)
65
- session_id_from_cookie(request) || generate_session_id(request)
66
- end
67
-
68
59
  def visitor_id_from_cookie(request)
69
60
  request.cookies[VISITOR_COOKIE_NAME]
70
61
  end
71
62
 
72
- def session_id_from_cookie(request)
73
- request.cookies[SESSION_COOKIE_NAME]
74
- end
75
-
76
63
  def user_id_from_session(request)
77
64
  request.session[SESSION_USER_ID_KEY] if request.session
78
65
  end
79
66
 
80
- def new_session?(request)
81
- session_id_from_cookie(request).nil?
82
- end
83
-
84
- def generate_session_id(request)
85
- existing_visitor_id = visitor_id_from_cookie(request)
86
-
87
- if existing_visitor_id
88
- Session::IdGenerator.generate_deterministic(visitor_id: existing_visitor_id)
89
- else
90
- Session::IdGenerator.generate_from_fingerprint(
91
- client_ip: client_ip(request),
92
- user_agent: user_agent(request)
93
- )
94
- end
95
- end
96
-
97
- def client_ip(request)
98
- request.env["HTTP_X_FORWARDED_FOR"]&.split(",")&.first&.strip ||
99
- request.env["HTTP_X_REAL_IP"] ||
100
- request.ip ||
101
- "unknown"
102
- end
103
-
104
- def user_agent(request)
105
- request.user_agent || "unknown"
106
- end
107
-
108
- # Session creation
109
-
110
- def create_session_if_new(context, request)
111
- return unless context[:new_session]
112
-
113
- create_session_async(context, request)
114
- end
115
-
116
- def create_session_async(context, request)
117
- # Capture values in local variables for thread safety
118
- visitor_id = context[:visitor_id]
119
- session_id = context[:session_id]
120
- url = request.url
121
- referrer = request.referer
122
-
123
- Thread.new do
124
- create_session(visitor_id, session_id, url, referrer)
125
- end
126
- end
127
-
128
- def create_session(visitor_id, session_id, url, referrer)
129
- Client.session(
130
- visitor_id: visitor_id,
131
- session_id: session_id,
132
- url: url,
133
- referrer: referrer
134
- )
135
- rescue => e
136
- log_session_error(e)
137
- end
138
-
139
- def log_session_error(error)
140
- Mbuzz.config.logger&.error("Session creation failed: #{error.message}")
141
- end
142
-
143
- # Cookie setting
144
-
145
- def set_cookies(headers, context, request)
146
- set_visitor_cookie(headers, context, request)
147
- set_session_cookie(headers, context, request)
148
- end
67
+ # Cookie setting - only visitor cookie (sessions are server-side)
149
68
 
150
69
  def set_visitor_cookie(headers, context, request)
151
70
  Rack::Utils.set_cookie_header!(
@@ -155,14 +74,6 @@ module Mbuzz
155
74
  )
156
75
  end
157
76
 
158
- def set_session_cookie(headers, context, request)
159
- Rack::Utils.set_cookie_header!(
160
- headers,
161
- SESSION_COOKIE_NAME,
162
- session_cookie_options(context, request)
163
- )
164
- end
165
-
166
77
  def visitor_cookie_options(context, request)
167
78
  base_cookie_options(request).merge(
168
79
  value: context[:visitor_id],
@@ -170,13 +81,6 @@ module Mbuzz
170
81
  )
171
82
  end
172
83
 
173
- def session_cookie_options(context, request)
174
- base_cookie_options(request).merge(
175
- value: context[:session_id],
176
- max_age: SESSION_COOKIE_MAX_AGE
177
- )
178
- end
179
-
180
84
  def base_cookie_options(request)
181
85
  options = {
182
86
  path: VISITOR_COOKIE_PATH,
@@ -33,6 +33,12 @@ module Mbuzz
33
33
  @request.user_agent
34
34
  end
35
35
 
36
+ def ip
37
+ @request.env["HTTP_X_FORWARDED_FOR"]&.split(",")&.first&.strip ||
38
+ @request.env["HTTP_X_REAL_IP"] ||
39
+ @request.ip
40
+ end
41
+
36
42
  def enriched_properties(custom = {})
37
43
  { url: url, referrer: referrer }.compact.merge(custom)
38
44
  end
data/lib/mbuzz/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Mbuzz
4
- VERSION = "0.6.7"
4
+ VERSION = "0.7.0"
5
5
  end
data/lib/mbuzz.rb CHANGED
@@ -3,7 +3,6 @@
3
3
  require_relative "mbuzz/version"
4
4
  require_relative "mbuzz/configuration"
5
5
  require_relative "mbuzz/visitor/identifier"
6
- require_relative "mbuzz/session/id_generator"
7
6
  require_relative "mbuzz/request_context"
8
7
  require_relative "mbuzz/api"
9
8
  require_relative "mbuzz/client"
@@ -18,20 +17,15 @@ module Mbuzz
18
17
  EVENTS_PATH = "/events"
19
18
  IDENTIFY_PATH = "/identify"
20
19
  CONVERSIONS_PATH = "/conversions"
21
- SESSIONS_PATH = "/sessions"
22
20
 
23
21
  VISITOR_COOKIE_NAME = "_mbuzz_vid"
24
22
  VISITOR_COOKIE_MAX_AGE = 60 * 60 * 24 * 365 * 2 # 2 years
25
23
  VISITOR_COOKIE_PATH = "/"
26
24
  VISITOR_COOKIE_SAME_SITE = "Lax"
27
25
 
28
- SESSION_COOKIE_NAME = "_mbuzz_sid"
29
- SESSION_COOKIE_MAX_AGE = 30 * 60 # 30 minutes
30
-
31
26
  SESSION_USER_ID_KEY = "user_id"
32
27
  ENV_USER_ID_KEY = "mbuzz.user_id"
33
28
  ENV_VISITOR_ID_KEY = "mbuzz.visitor_id"
34
- ENV_SESSION_ID_KEY = "mbuzz.session_id"
35
29
 
36
30
  # ============================================================================
37
31
  # Configuration
@@ -81,10 +75,6 @@ module Mbuzz
81
75
  RequestContext.current&.request&.env&.dig(ENV_USER_ID_KEY)
82
76
  end
83
77
 
84
- def self.session_id
85
- RequestContext.current&.request&.env&.dig(ENV_SESSION_ID_KEY)
86
- end
87
-
88
78
  # ============================================================================
89
79
  # 4-Call Model API
90
80
  # ============================================================================
@@ -93,18 +83,24 @@ module Mbuzz
93
83
  #
94
84
  # @param event_type [String] The name of the event
95
85
  # @param properties [Hash] Custom event properties (url, referrer auto-added)
86
+ # @param identifier [Hash, nil] Optional identifier for cross-device identity resolution
96
87
  # @return [Hash, false] Result hash on success, false on failure
97
88
  #
98
89
  # @example
99
90
  # Mbuzz.event("add_to_cart", product_id: "SKU-123", price: 49.99)
100
91
  #
101
- def self.event(event_type, **properties)
92
+ # @example With identifier for cross-device tracking
93
+ # Mbuzz.event("page_view", identifier: { email: "user@example.com" })
94
+ #
95
+ def self.event(event_type, identifier: nil, **properties)
102
96
  Client.track(
103
97
  visitor_id: visitor_id,
104
- session_id: session_id,
105
98
  user_id: user_id,
106
99
  event_type: event_type,
107
- properties: enriched_properties(properties)
100
+ properties: enriched_properties(properties),
101
+ ip: current_ip,
102
+ user_agent: current_user_agent,
103
+ identifier: identifier
108
104
  )
109
105
  end
110
106
 
@@ -121,6 +117,7 @@ module Mbuzz
121
117
  # @param user_id [String, nil] User ID for acquisition-linked conversions
122
118
  # @param is_acquisition [Boolean] Mark this as the acquisition conversion for this user
123
119
  # @param inherit_acquisition [Boolean] Inherit attribution from user's acquisition conversion
120
+ # @param identifier [Hash, nil] Optional identifier for cross-device identity resolution
124
121
  # @param properties [Hash] Custom properties
125
122
  # @return [Hash, false] Result hash on success, false on failure
126
123
  #
@@ -133,7 +130,10 @@ module Mbuzz
133
130
  # @example Recurring revenue (inherits attribution from acquisition)
134
131
  # Mbuzz.conversion("payment", user_id: "user_123", revenue: 49.00, inherit_acquisition: true)
135
132
  #
136
- def self.conversion(conversion_type, revenue: nil, user_id: nil, is_acquisition: false, inherit_acquisition: false, **properties)
133
+ # @example With identifier for cross-device tracking
134
+ # Mbuzz.conversion("purchase", identifier: { email: "user@example.com" })
135
+ #
136
+ def self.conversion(conversion_type, revenue: nil, user_id: nil, is_acquisition: false, inherit_acquisition: false, identifier: nil, **properties)
137
137
  Client.conversion(
138
138
  visitor_id: visitor_id,
139
139
  user_id: user_id,
@@ -141,7 +141,10 @@ module Mbuzz
141
141
  revenue: revenue,
142
142
  is_acquisition: is_acquisition,
143
143
  inherit_acquisition: inherit_acquisition,
144
- properties: enriched_properties(properties)
144
+ properties: enriched_properties(properties),
145
+ ip: current_ip,
146
+ user_agent: current_user_agent,
147
+ identifier: identifier
145
148
  )
146
149
  end
147
150
 
@@ -176,4 +179,14 @@ module Mbuzz
176
179
  RequestContext.current.enriched_properties(custom_properties)
177
180
  end
178
181
  private_class_method :enriched_properties
182
+
183
+ def self.current_ip
184
+ RequestContext.current&.ip
185
+ end
186
+ private_class_method :current_ip
187
+
188
+ def self.current_user_agent
189
+ RequestContext.current&.user_agent
190
+ end
191
+ private_class_method :current_user_agent
179
192
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mbuzz
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.7
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - mbuzz team
@@ -44,14 +44,12 @@ files:
44
44
  - lib/mbuzz/client.rb
45
45
  - lib/mbuzz/client/conversion_request.rb
46
46
  - lib/mbuzz/client/identify_request.rb
47
- - lib/mbuzz/client/session_request.rb
48
47
  - lib/mbuzz/client/track_request.rb
49
48
  - lib/mbuzz/configuration.rb
50
49
  - lib/mbuzz/controller_helpers.rb
51
50
  - lib/mbuzz/middleware/tracking.rb
52
51
  - lib/mbuzz/railtie.rb
53
52
  - lib/mbuzz/request_context.rb
54
- - lib/mbuzz/session/id_generator.rb
55
53
  - lib/mbuzz/version.rb
56
54
  - lib/mbuzz/visitor/identifier.rb
57
55
  - lib/specs/old/SPECIFICATION.md
@@ -1,43 +0,0 @@
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
@@ -1,33 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "digest"
4
- require "securerandom"
5
-
6
- module Mbuzz
7
- module Session
8
- class IdGenerator
9
- SESSION_TIMEOUT_SECONDS = 1800
10
- SESSION_ID_LENGTH = 64
11
- FINGERPRINT_LENGTH = 32
12
-
13
- class << self
14
- def generate_deterministic(visitor_id:, timestamp: Time.now.to_i)
15
- time_bucket = timestamp / SESSION_TIMEOUT_SECONDS
16
- raw = "#{visitor_id}_#{time_bucket}"
17
- Digest::SHA256.hexdigest(raw)[0, SESSION_ID_LENGTH]
18
- end
19
-
20
- def generate_from_fingerprint(client_ip:, user_agent:, timestamp: Time.now.to_i)
21
- fingerprint = Digest::SHA256.hexdigest("#{client_ip}|#{user_agent}")[0, FINGERPRINT_LENGTH]
22
- time_bucket = timestamp / SESSION_TIMEOUT_SECONDS
23
- raw = "#{fingerprint}_#{time_bucket}"
24
- Digest::SHA256.hexdigest(raw)[0, SESSION_ID_LENGTH]
25
- end
26
-
27
- def generate_random
28
- SecureRandom.hex(32)
29
- end
30
- end
31
- end
32
- end
33
- end