track_relay 1.0.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/CHANGELOG.md +37 -0
- data/README.md +40 -0
- data/lib/track_relay/client_id/ga.rb +30 -7
- data/lib/track_relay/configuration.rb +31 -1
- data/lib/track_relay/instrumenter.rb +16 -0
- data/lib/track_relay/subscribers/base.rb +20 -0
- data/lib/track_relay/subscribers/ga4_measurement_protocol.rb +77 -0
- data/lib/track_relay/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 918577fceae3974911227a1b988f8d17ecf636c086b7f91ad9b8e36ac6d2b9fd
|
|
4
|
+
data.tar.gz: 1dcd1c4f9bb900b0b3945a40ae0222b3205212f52477e10640baa0facc39ae40
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c95d0181afcad7811afaff649155f78681f41a2d54fc3f3ebaf1057f56e4b146d5704a6954e83183d9ef78269a807b648545cc493b42e22c9b93dd53084c9fd2
|
|
7
|
+
data.tar.gz: 9d0c93c207fbdfa07237d9f25657053626711ac0eedf576fd0bb36db84203b67e716bd0f382face10074cbacb4a24842a7e56ac1c7b7adc82ac59aed66036fd3
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,42 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.1.0] - 2026-07-07
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- `config.ga4_require_browser_client_id` (default `false`) — browser-proof
|
|
14
|
+
delivery gate for `Subscribers::Ga4MeasurementProtocol`. When enabled,
|
|
15
|
+
events are delivered only when the current request carries a genuine
|
|
16
|
+
`_ga` cookie (set by gtag JS, which bots don't execute). No cookie — or a
|
|
17
|
+
malformed one with fewer than four dot-segments — enqueues no
|
|
18
|
+
`DeliveryJob` at all; the random per-event client_id fallback never
|
|
19
|
+
fires. The cookie-derived client_id rides in the delivered payload's
|
|
20
|
+
context.
|
|
21
|
+
- `config.ga4_enrich_page_context` (default `false`) — notification-time
|
|
22
|
+
page/session enrichment for GA4 MP events: `page_location` (request
|
|
23
|
+
URL), `page_referrer` (when a referer exists), and `session_id` +
|
|
24
|
+
`engagement_time_msec` (nominal 100) when the gtag `_ga_<stream>`
|
|
25
|
+
session cookie is parseable. Captured while the request is in scope and
|
|
26
|
+
serialized through the payload, so async deliveries carry it. Without
|
|
27
|
+
these params GA4 files server-side events under a blank page path.
|
|
28
|
+
- `config.track_gate` (default `nil`) — host-level fan-out gate. An
|
|
29
|
+
optional callable receiving `payload:` and `request:` keywords,
|
|
30
|
+
evaluated once per `TrackRelay.track` call before the event notification
|
|
31
|
+
fires; a falsy return drops the event for every subscriber. Enables
|
|
32
|
+
e.g. "only track requests that prove a real browser" across Ahoy, GA4,
|
|
33
|
+
and Logger at once.
|
|
34
|
+
- `Subscribers::Base#prepare(payload)` — public notification-time hook
|
|
35
|
+
(filter → prepare → sync/async) for subclasses to gate delivery
|
|
36
|
+
(return `nil`) or swap in an enriched copy of the payload while the
|
|
37
|
+
request is still in scope.
|
|
38
|
+
- `ClientId::Ga.parse` / `ClientId::Ga.from_request` — the `_ga` parse
|
|
39
|
+
rule as reusable class methods (the resolver-chain `#call` now
|
|
40
|
+
delegates to them).
|
|
41
|
+
|
|
42
|
+
### Changed
|
|
43
|
+
- All new options default off: an unconfigured upgrade is byte-identical
|
|
44
|
+
to 1.0.0 (the pre-existing test suite passes unmodified).
|
|
45
|
+
|
|
10
46
|
## [1.0.0] - 2026-05-07
|
|
11
47
|
|
|
12
48
|
### Added
|
|
@@ -141,6 +177,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
141
177
|
- Privacy: untyped JSONL captures param NAMES only (never VALUES) to avoid leaking PII.
|
|
142
178
|
- Naming: `track_relay` availability on RubyGems will be re-validated before 1.0.
|
|
143
179
|
|
|
180
|
+
[1.1.0]: https://github.com/dchuk/track_relay/compare/v1.0.0...v1.1.0
|
|
144
181
|
[1.0.0]: https://github.com/dchuk/track_relay/compare/v0.3.0...v1.0.0
|
|
145
182
|
[0.3.0]: https://github.com/dchuk/track_relay/releases/tag/v0.3.0
|
|
146
183
|
[0.2.0]: https://github.com/dchuk/track_relay/releases/tag/v0.2.0
|
data/README.md
CHANGED
|
@@ -334,6 +334,46 @@ TrackRelay.subscribe(
|
|
|
334
334
|
)
|
|
335
335
|
```
|
|
336
336
|
|
|
337
|
+
### Browser-proof gating + page context (opt-in)
|
|
338
|
+
|
|
339
|
+
Server-side GA4 events have two chronic problems: bots with browser user agents inflate user counts (the Measurement Protocol accepts any client_id, and the subscriber's fallback mints a random one per event), and events land under a blank page path because nothing sends `page_location`. Two opt-in flags fix both:
|
|
340
|
+
|
|
341
|
+
```ruby
|
|
342
|
+
TrackRelay.configure do |c|
|
|
343
|
+
# Deliver ONLY when the current request carries a genuine `_ga`
|
|
344
|
+
# cookie — set by gtag JS, which bots don't execute. No cookie (or a
|
|
345
|
+
# malformed one) means no DeliveryJob is enqueued at all; the random
|
|
346
|
+
# client_id fallback never fires. The cookie-derived client_id is
|
|
347
|
+
# used for the delivery.
|
|
348
|
+
c.ga4_require_browser_client_id = true
|
|
349
|
+
|
|
350
|
+
# Capture page context from the request at notification time and
|
|
351
|
+
# send it with every GA4 event: `page_location` (request URL),
|
|
352
|
+
# `page_referrer` (when a referer exists), and `session_id` +
|
|
353
|
+
# `engagement_time_msec` when the gtag `_ga_<stream>` session cookie
|
|
354
|
+
# is present.
|
|
355
|
+
c.ga4_enrich_page_context = true
|
|
356
|
+
end
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
Both run inside the subscriber's notification-time `prepare` hook — the request is still in scope there, and the captured values ride through the serialized payload to the async `DeliveryJob`. Other subscribers always receive the original, unmodified payload.
|
|
360
|
+
|
|
361
|
+
### Host-level track gate (opt-in)
|
|
362
|
+
|
|
363
|
+
`config.track_gate` is evaluated once per `TrackRelay.track` call, before the event fans out to **any** subscriber. A falsy return drops the event everywhere (Ahoy, GA4, Logger, …). It receives `payload:` and `request:` keywords:
|
|
364
|
+
|
|
365
|
+
```ruby
|
|
366
|
+
TrackRelay.configure do |c|
|
|
367
|
+
# Only track requests that prove a real browser — aligns first-party
|
|
368
|
+
# analytics (e.g. Ahoy) with what GA4 sees under the browser gate.
|
|
369
|
+
c.track_gate = ->(payload:, request:) {
|
|
370
|
+
TrackRelay::ClientId::Ga.from_request(request)
|
|
371
|
+
}
|
|
372
|
+
end
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
Unset (`nil`, the default) disables the gate entirely.
|
|
376
|
+
|
|
337
377
|
### `client_id` resolver chain
|
|
338
378
|
|
|
339
379
|
`TrackRelay::Current.client_id` is resolved via a configurable chain of `client_id_resolvers`. The default chain checks the GA `_ga` cookie, then any Ahoy visitor token, then mints a session-stable UUID into `session[:track_relay_client_id]` so visitors without a `_ga` cookie still get a stable identifier. First non-nil wins; per-resolver exceptions are isolated so a single buggy resolver cannot block the chain.
|
|
@@ -28,6 +28,35 @@ module TrackRelay
|
|
|
28
28
|
# and yield `nil` (callers should fall through to the next resolver
|
|
29
29
|
# in the chain).
|
|
30
30
|
class Ga
|
|
31
|
+
# Parse a raw `_ga` cookie value into a GA4 client_id.
|
|
32
|
+
#
|
|
33
|
+
# This is THE canonical `_ga` parse rule — the resolver chain
|
|
34
|
+
# ({#call}) and the browser-proof delivery gate
|
|
35
|
+
# ({Subscribers::Ga4MeasurementProtocol#prepare}) both delegate
|
|
36
|
+
# here so the "fewer than four segments = malformed = nil"
|
|
37
|
+
# posture is defined exactly once.
|
|
38
|
+
#
|
|
39
|
+
# @param cookie_value [String, nil] raw `_ga` cookie value
|
|
40
|
+
# @return [String, nil] the parsed GA4 client_id, or `nil` when
|
|
41
|
+
# the value is missing/empty/malformed.
|
|
42
|
+
def self.parse(cookie_value)
|
|
43
|
+
return nil if cookie_value.nil? || cookie_value.empty?
|
|
44
|
+
|
|
45
|
+
parts = cookie_value.split(".")
|
|
46
|
+
return nil if parts.size < 4
|
|
47
|
+
|
|
48
|
+
"#{parts[-2]}.#{parts[-1]}"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Extract and parse the `_ga` cookie from a request-like object.
|
|
52
|
+
#
|
|
53
|
+
# @param request [#cookies, nil] any object exposing
|
|
54
|
+
# `request.cookies["_ga"]` (e.g. an `ActionDispatch::Request`).
|
|
55
|
+
# @return [String, nil]
|
|
56
|
+
def self.from_request(request)
|
|
57
|
+
parse(request&.cookies&.[]("_ga"))
|
|
58
|
+
end
|
|
59
|
+
|
|
31
60
|
# @param controller [#request] any object exposing
|
|
32
61
|
# `controller.request.cookies["_ga"]`. Typically an
|
|
33
62
|
# `ActionController::Base` instance from
|
|
@@ -35,13 +64,7 @@ module TrackRelay
|
|
|
35
64
|
# @return [String, nil] the parsed GA4 client_id, or `nil` when the
|
|
36
65
|
# cookie is missing/empty/malformed.
|
|
37
66
|
def call(controller:, **)
|
|
38
|
-
|
|
39
|
-
return nil if ga_cookie.nil? || ga_cookie.empty?
|
|
40
|
-
|
|
41
|
-
parts = ga_cookie.split(".")
|
|
42
|
-
return nil if parts.size < 4
|
|
43
|
-
|
|
44
|
-
"#{parts[-2]}.#{parts[-1]}"
|
|
67
|
+
self.class.from_request(controller&.request)
|
|
45
68
|
end
|
|
46
69
|
end
|
|
47
70
|
end
|
|
@@ -50,6 +50,30 @@ module TrackRelay
|
|
|
50
50
|
# When `true`, the GA4 subscriber posts to
|
|
51
51
|
# `https://region1.google-analytics.com/mp/collect` instead of
|
|
52
52
|
# the global endpoint. Defaults to `false`.
|
|
53
|
+
# @!attribute [rw] ga4_require_browser_client_id
|
|
54
|
+
# When `true`, the GA4 subscriber delivers ONLY when the current
|
|
55
|
+
# request carries a genuine `_ga` cookie (set by gtag JS, which
|
|
56
|
+
# bots don't execute). No cookie — or a malformed one — means no
|
|
57
|
+
# DeliveryJob is enqueued at all; the random client_id fallback
|
|
58
|
+
# never fires. The cookie-derived client_id is merged into the
|
|
59
|
+
# delivered payload's context. Defaults to `false` (deliver
|
|
60
|
+
# everything, as before).
|
|
61
|
+
# @!attribute [rw] ga4_enrich_page_context
|
|
62
|
+
# When `true`, the GA4 subscriber captures page context from the
|
|
63
|
+
# current request at notification time and merges it into the
|
|
64
|
+
# delivered event's params: `page_location` (request URL),
|
|
65
|
+
# `page_referrer` (when a referer exists), and — when the gtag
|
|
66
|
+
# session cookie `_ga_<stream>` is parseable — `session_id` plus
|
|
67
|
+
# a nominal `engagement_time_msec`. Without these GA4 files
|
|
68
|
+
# server-side events under a blank page path. Defaults to
|
|
69
|
+
# `false`.
|
|
70
|
+
# @!attribute [rw] track_gate
|
|
71
|
+
# Optional callable evaluated once per {TrackRelay.track} call,
|
|
72
|
+
# BEFORE the event notification fans out to any subscriber.
|
|
73
|
+
# Receives `payload:` (the built {EventPayload}) and `request:`
|
|
74
|
+
# ({Current.request}, possibly `nil`) keywords; a falsy return
|
|
75
|
+
# drops the event for every subscriber. `nil` (the default)
|
|
76
|
+
# disables the gate entirely — current behavior.
|
|
53
77
|
attr_accessor :swallow_subscriber_errors,
|
|
54
78
|
:untyped_log_path,
|
|
55
79
|
:untyped_events_allowed,
|
|
@@ -58,7 +82,10 @@ module TrackRelay
|
|
|
58
82
|
:client_id_resolvers,
|
|
59
83
|
:ga4_measurement_id,
|
|
60
84
|
:ga4_api_secret,
|
|
61
|
-
:ga4_use_eu_endpoint
|
|
85
|
+
:ga4_use_eu_endpoint,
|
|
86
|
+
:ga4_require_browser_client_id,
|
|
87
|
+
:ga4_enrich_page_context,
|
|
88
|
+
:track_gate
|
|
62
89
|
|
|
63
90
|
# @return [Array] registered subscriber instances, in insertion order
|
|
64
91
|
attr_reader :subscribers
|
|
@@ -82,6 +109,9 @@ module TrackRelay
|
|
|
82
109
|
@ga4_measurement_id = nil
|
|
83
110
|
@ga4_api_secret = nil
|
|
84
111
|
@ga4_use_eu_endpoint = false
|
|
112
|
+
@ga4_require_browser_client_id = false
|
|
113
|
+
@ga4_enrich_page_context = false
|
|
114
|
+
@track_gate = nil
|
|
85
115
|
end
|
|
86
116
|
|
|
87
117
|
# Append a subscriber to the registry.
|
|
@@ -82,6 +82,7 @@ module TrackRelay
|
|
|
82
82
|
extra_context: direct_context
|
|
83
83
|
)
|
|
84
84
|
return unless validate(payload)
|
|
85
|
+
return unless track_gate_allows?(payload)
|
|
85
86
|
ActiveSupport::Notifications.instrument(NOTIFICATION, event: payload)
|
|
86
87
|
end
|
|
87
88
|
end
|
|
@@ -215,6 +216,21 @@ module TrackRelay
|
|
|
215
216
|
}
|
|
216
217
|
end
|
|
217
218
|
|
|
219
|
+
# Evaluate {Configuration#track_gate} once per track call, inside
|
|
220
|
+
# the `Current.set` scope so the gate sees the same request the
|
|
221
|
+
# subscribers would. Runs AFTER validation and BEFORE the
|
|
222
|
+
# notification fans out — a falsy return means no subscriber ever
|
|
223
|
+
# sees the event. Unset gate (`nil`) always allows.
|
|
224
|
+
#
|
|
225
|
+
# @param payload [EventPayload]
|
|
226
|
+
# @return [Boolean]
|
|
227
|
+
def track_gate_allows?(payload)
|
|
228
|
+
gate = TrackRelay.config.track_gate
|
|
229
|
+
return true if gate.nil?
|
|
230
|
+
|
|
231
|
+
!!gate.call(payload: payload, request: Current.request)
|
|
232
|
+
end
|
|
233
|
+
|
|
218
234
|
# Run {EventPayload#validate!} and apply the
|
|
219
235
|
# {Configuration#raise_on_validation_error} gate.
|
|
220
236
|
#
|
|
@@ -109,6 +109,9 @@ module TrackRelay
|
|
|
109
109
|
def handle(payload)
|
|
110
110
|
return nil if filtered?(payload.name.to_sym)
|
|
111
111
|
|
|
112
|
+
payload = prepare(payload)
|
|
113
|
+
return nil if payload.nil?
|
|
114
|
+
|
|
112
115
|
if self.class.synchronous || TrackRelay.config.force_synchronous
|
|
113
116
|
safe_deliver(payload)
|
|
114
117
|
else
|
|
@@ -117,6 +120,23 @@ module TrackRelay
|
|
|
117
120
|
end
|
|
118
121
|
end
|
|
119
122
|
|
|
123
|
+
# Notification-time hook, called by {#handle} after the event-name
|
|
124
|
+
# filter and BEFORE the sync/async branch — i.e. while the request
|
|
125
|
+
# ({Current.request}) is still in scope, which is gone by the time
|
|
126
|
+
# an async {DeliveryJob} runs.
|
|
127
|
+
#
|
|
128
|
+
# Subclasses override this to gate delivery (return `nil` to drop
|
|
129
|
+
# the event silently — no job, no `deliver`) or to swap in an
|
|
130
|
+
# enriched **copy** of the payload. The same payload object fans
|
|
131
|
+
# out to every subscriber, so implementations must never mutate
|
|
132
|
+
# `payload` — build a new {EventPayload} instead.
|
|
133
|
+
#
|
|
134
|
+
# @param payload [EventPayload]
|
|
135
|
+
# @return [EventPayload, nil] the payload to deliver, or `nil` to drop
|
|
136
|
+
def prepare(payload)
|
|
137
|
+
payload
|
|
138
|
+
end
|
|
139
|
+
|
|
120
140
|
# Wrap {#deliver} with the per-subscriber rescue.
|
|
121
141
|
#
|
|
122
142
|
# Returns `nil` on success or the StandardError on failure. ALWAYS
|
|
@@ -35,6 +35,12 @@ module TrackRelay
|
|
|
35
35
|
# - `config.ga4_api_secret` — per-stream MP secret
|
|
36
36
|
# - `config.ga4_use_eu_endpoint` — when `true`, post to
|
|
37
37
|
# `https://region1.google-analytics.com/mp/collect`
|
|
38
|
+
# - `config.ga4_require_browser_client_id` — when `true`, deliver
|
|
39
|
+
# only for requests carrying a genuine `_ga` cookie (see
|
|
40
|
+
# {#prepare})
|
|
41
|
+
# - `config.ga4_enrich_page_context` — when `true`, merge
|
|
42
|
+
# `page_location` / `page_referrer` / `session_id` /
|
|
43
|
+
# `engagement_time_msec` into delivered params (see {#prepare})
|
|
38
44
|
#
|
|
39
45
|
# When `ga4_measurement_id` or `ga4_api_secret` is `nil`, `#deliver`
|
|
40
46
|
# emits a single `Rails.logger.warn` and returns without raising —
|
|
@@ -85,6 +91,46 @@ module TrackRelay
|
|
|
85
91
|
# drops them.
|
|
86
92
|
RESERVED_PARAM_PREFIXES = %w[firebase_ ga_ google_].freeze
|
|
87
93
|
|
|
94
|
+
# Nominal engagement time attached alongside `session_id` when
|
|
95
|
+
# page-context enrichment finds a gtag session cookie. GA4 needs
|
|
96
|
+
# a nonzero value for server events to count toward session
|
|
97
|
+
# activity; the number itself is nominal (Google's Measurement
|
|
98
|
+
# Protocol examples use 100).
|
|
99
|
+
ENGAGEMENT_TIME_MSEC = 100
|
|
100
|
+
|
|
101
|
+
# Browser-proof gate + page-context enrichment (issue #1), both
|
|
102
|
+
# opt-in via configuration. Runs at notification time —
|
|
103
|
+
# {Current.request} is gone by the time an async {DeliveryJob}
|
|
104
|
+
# performs, so gating and enrichment MUST happen here and ride
|
|
105
|
+
# through the serialized payload.
|
|
106
|
+
#
|
|
107
|
+
# - `config.ga4_require_browser_client_id`: deliver only when the
|
|
108
|
+
# current request carries a genuine `_ga` cookie; the
|
|
109
|
+
# cookie-derived client_id lands in the delivered context.
|
|
110
|
+
# - `config.ga4_enrich_page_context`: merge `page_location`,
|
|
111
|
+
# conditional `page_referrer`, and conditional `session_id` /
|
|
112
|
+
# `engagement_time_msec` into the delivered params.
|
|
113
|
+
#
|
|
114
|
+
# Either path swaps in a **copy** — the shared payload other
|
|
115
|
+
# subscribers receive during fan-out is never mutated.
|
|
116
|
+
#
|
|
117
|
+
# @param payload [EventPayload]
|
|
118
|
+
# @return [EventPayload, nil] `nil` drops the event (no job)
|
|
119
|
+
def prepare(payload)
|
|
120
|
+
config = TrackRelay.config
|
|
121
|
+
return payload unless config.ga4_require_browser_client_id || config.ga4_enrich_page_context
|
|
122
|
+
|
|
123
|
+
request = Current.request
|
|
124
|
+
client_id = ClientId::Ga.from_request(request)
|
|
125
|
+
return nil if config.ga4_require_browser_client_id && client_id.nil?
|
|
126
|
+
return payload if request.nil?
|
|
127
|
+
|
|
128
|
+
hash = payload.to_h
|
|
129
|
+
hash[:params] = hash[:params].merge(page_params(request)) if config.ga4_enrich_page_context
|
|
130
|
+
hash[:context] = hash[:context].merge(client_id: client_id) if client_id
|
|
131
|
+
EventPayload.from_h(hash)
|
|
132
|
+
end
|
|
133
|
+
|
|
88
134
|
# POST `payload` to the GA4 Measurement Protocol endpoint.
|
|
89
135
|
#
|
|
90
136
|
# See class docs for the full configuration / error contract.
|
|
@@ -112,6 +158,37 @@ module TrackRelay
|
|
|
112
158
|
|
|
113
159
|
private
|
|
114
160
|
|
|
161
|
+
# Page-context params captured from the live request (only when
|
|
162
|
+
# `config.ga4_enrich_page_context` is enabled).
|
|
163
|
+
def page_params(request)
|
|
164
|
+
params = {page_location: request.original_url}
|
|
165
|
+
params[:page_referrer] = request.referer if request.referer && !request.referer.empty?
|
|
166
|
+
|
|
167
|
+
if (session_id = ga_session_id(request))
|
|
168
|
+
params[:session_id] = session_id
|
|
169
|
+
params[:engagement_time_msec] = ENGAGEMENT_TIME_MSEC
|
|
170
|
+
end
|
|
171
|
+
params
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# The gtag session cookie is `_ga_<stream>` (measurement id minus
|
|
175
|
+
# the "G-" prefix), shaped `GS1.1.<session_id>.<session_number>...`.
|
|
176
|
+
# The format is Google-controlled, so parse defensively — too few
|
|
177
|
+
# segments or a blank session segment yields nil and the delivery
|
|
178
|
+
# proceeds without session params.
|
|
179
|
+
def ga_session_id(request)
|
|
180
|
+
stream = TrackRelay.config.ga4_measurement_id.to_s.delete_prefix("G-")
|
|
181
|
+
return nil if stream.empty?
|
|
182
|
+
|
|
183
|
+
cookie = request.cookies["_ga_#{stream}"]
|
|
184
|
+
return nil if cookie.nil? || cookie.empty?
|
|
185
|
+
|
|
186
|
+
parts = cookie.split(".")
|
|
187
|
+
return nil if parts.size < 4 || parts[2].nil? || parts[2].empty?
|
|
188
|
+
|
|
189
|
+
parts[2]
|
|
190
|
+
end
|
|
191
|
+
|
|
115
192
|
# Run call-time payload constraint checks (REQ-27 split). Returns
|
|
116
193
|
# `true` when delivery should proceed, `false` when a constraint
|
|
117
194
|
# was violated AND `raise_on_validation_error` is `false` (the
|
data/lib/track_relay/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: track_relay
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
|
-
-
|
|
7
|
+
- dchuk
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|