track_relay 1.2.0 → 1.4.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: aaa3f984be840d9f657d23e543aae04eccac6c45da4877fcdaad30e8f3dc7f7a
4
- data.tar.gz: ed659de0a4cd9e7a63d50d8d6ddeb32c962c667471e4c9b56f143fcf16f0b26b
3
+ metadata.gz: d7fca8b3efc2716f3ba667aecdadd66bf58f2cbbd18a76f28dd04784a521ffdf
4
+ data.tar.gz: 01103c51038bf8fbefd95e63b513223ea93407e6583f4925ed3756805209a670
5
5
  SHA512:
6
- metadata.gz: 19dd615e0a99f54df3bedaf1eb01b61ace7a338fa04d4d77373cbb43b9e786e221ade99ea3fd52e13b82a64be705a5ec2674bdc3fb69223d84e2b25157da272b
7
- data.tar.gz: eabf9d5db4cb4da4643815b628cc7c065b6b93b4f4ad8345b2d4525a93c6ea982f7877db25fae2df294bef512a222cdfb3b25966988bd5f13f9e54ad58b70646
6
+ metadata.gz: a36d9e2d83ea2215798bc5ed963b0e618aa29436c83ad6e0d72ee0f46472435300fcfe0ae4fb9eecc29e491c25ca258e5f7cc6b85110f17f959cd6e9af3c4cc1
7
+ data.tar.gz: 0b855b5604793c877501ee2892a1f0d374384b257391b27f5cc1f453f8064a885acb5b874b5df8de97340dab64deb4ac808c625d749ed3961f8d213db8afc68d
data/CHANGELOG.md CHANGED
@@ -7,6 +7,38 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.4.0] - 2026-07-31
11
+
12
+ ### Added
13
+ - `config.manifest_path` — hosts can relocate the generated event-schema
14
+ manifest outside the public web root (the on-disk catalog discloses
15
+ internal event names/params when served world-readable). Defaults to
16
+ `nil`, preserving the existing `public/track_relay_catalog.json`
17
+ behavior. The `track_relay:manifest` rake task, the development
18
+ catalog hot-reload regeneration, and the `assets:precompile` chaining
19
+ all honor it, since every default-path write flows through
20
+ `TrackRelay::Manifest.write!`.
21
+
22
+ ## [1.3.0] - 2026-07-31
23
+
24
+ ### Added
25
+ - `TrackRelay::PageViewTracking` — opt-in automatic server-side
26
+ `page_view` emission for hosts delivering GA4 purely via the
27
+ Measurement Protocol (no client gtag). `include
28
+ TrackRelay::PageViewTracking` + `track_page_views` emits exactly one
29
+ `page_view` per full HTML page render — never for JSON/non-HTML,
30
+ turbo-frame fetches, turbo-stream responses, redirects, non-GETs, or
31
+ unsuccessful responses. A host suppression hook (`track_page_views
32
+ if: :method_name` or a proc, instance-exec'd per request) is where
33
+ tracking policy (opt-out, geo, bot checks) plugs in. Events ride the
34
+ normal `TrackRelay.track` path, so `track_gate`, subscribers, and
35
+ GA4 page-context enrichment apply unchanged.
36
+ - The builtin `page_view` event is registered directly with the
37
+ catalog (and therefore the manifest), deliberately bypassing the
38
+ DSL's GA4 reserved-name validation: that guard protects *custom*
39
+ events from shadowing gtag auto-collection, but the MP-standard
40
+ `page_view` is exactly what a server-side host must send.
41
+
10
42
  ## [1.2.0] - 2026-07-31
11
43
 
12
44
  ### Added
@@ -64,7 +64,7 @@ namespace :track_relay do
64
64
  exit(clean ? 0 : 1)
65
65
  end
66
66
 
67
- desc "Generate public/track_relay_catalog.json from the loaded catalog"
67
+ desc "Generate the catalog manifest (config.manifest_path, default public/track_relay_catalog.json)"
68
68
  task manifest: :environment do
69
69
  # Footgun guard (RISK-04): an empty manifest tells the JS client
70
70
  # "no schema, accept everything" — silently. Abort loudly so the
@@ -87,7 +87,8 @@ module TrackRelay
87
87
  :ga4_enrich_page_context,
88
88
  :track_gate,
89
89
  :signed_client_token_secret,
90
- :signed_client_token_ttl
90
+ :signed_client_token_ttl,
91
+ :manifest_path
91
92
 
92
93
  # @return [Array] registered subscriber instances, in insertion order
93
94
  attr_reader :subscribers
@@ -116,6 +117,7 @@ module TrackRelay
116
117
  @track_gate = nil
117
118
  @signed_client_token_secret = nil
118
119
  @signed_client_token_ttl = 30.days
120
+ @manifest_path = nil
119
121
  end
120
122
 
121
123
  # Append a subscriber to the registry.
@@ -74,6 +74,15 @@ module TrackRelay
74
74
  private
75
75
 
76
76
  def default_path
77
+ # `config.manifest_path` lets hosts keep the event schema out of
78
+ # the public web root; the rake task, dev reload, and
79
+ # assets:precompile chaining all resolve through here. The
80
+ # respond_to? guard keeps this file loadable standalone (the
81
+ # rake file require_relative's it without the umbrella require).
82
+ if TrackRelay.respond_to?(:config) && (configured = TrackRelay.config.manifest_path)
83
+ return configured
84
+ end
85
+
77
86
  unless defined?(Rails) && Rails.respond_to?(:root) && Rails.root
78
87
  raise ArgumentError,
79
88
  "TrackRelay::Manifest.write! requires a `path:` argument when Rails.root is unavailable"
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/concern"
4
+
5
+ module TrackRelay
6
+ # Opt-in automatic server-side `page_view` emission.
7
+ #
8
+ # Hosts that deliver GA4 purely via the Measurement Protocol (no
9
+ # client-side gtag) still need standard `page_view` events for GA4's
10
+ # page and engagement reports. Include this concern and call the
11
+ # macro:
12
+ #
13
+ # class ApplicationController < ActionController::Base
14
+ # include TrackRelay::PageViewTracking
15
+ # track_page_views
16
+ # end
17
+ #
18
+ # Events go through the normal {TrackRelay.track} path, so the host's
19
+ # `track_gate`, subscribers, and GA4 page-context enrichment apply
20
+ # unchanged.
21
+ #
22
+ # ## The builtin `page_view` catalog entry
23
+ #
24
+ # `page_view` sits in {TrackRelay::GA4_RESERVED_NAMES}, which guards
25
+ # *custom* catalog events from shadowing gtag's auto-collected ones.
26
+ # A server-side host has no auto-collection — the MP-standard
27
+ # `page_view` is exactly what it must send — so the concern registers
28
+ # the definition directly with the catalog, bypassing the DSL's
29
+ # reserved-name validation on purpose.
30
+ module PageViewTracking
31
+ extend ActiveSupport::Concern
32
+
33
+ include ControllerTracking
34
+
35
+ # Register the builtin `page_view` {EventDefinition} (no params —
36
+ # page context is attached by GA4 enrichment at delivery time).
37
+ # Idempotent so it can be re-run after test-suite `Catalog.clear!`.
38
+ #
39
+ # @return [void]
40
+ def self.register_builtin_event!
41
+ return if Catalog.defined?(:page_view)
42
+
43
+ Catalog.register(EventDefinition.new(name: :page_view))
44
+ end
45
+
46
+ included do
47
+ class_attribute :_track_relay_page_view_condition,
48
+ instance_accessor: false, instance_predicate: false, default: nil
49
+ end
50
+
51
+ class_methods do
52
+ # Enable automatic page_view emission for this controller (and
53
+ # subclasses).
54
+ #
55
+ # @param if [Symbol, Proc, nil] host-side suppression hook —
56
+ # a method name or proc evaluated in the controller instance
57
+ # per request; a falsy result suppresses the page_view.
58
+ # Tracking *policy* (opt-out, geo, bot checks) belongs to the
59
+ # host; this is where it plugs in.
60
+ # @return [void]
61
+ def track_page_views(if: nil)
62
+ self._track_relay_page_view_condition = binding.local_variable_get(:if)
63
+ PageViewTracking.register_builtin_event!
64
+ after_action :_track_relay_emit_page_view
65
+ end
66
+ end
67
+
68
+ private
69
+
70
+ def _track_relay_emit_page_view
71
+ return unless _track_relay_page_view_response?
72
+ return unless _track_relay_page_view_allowed?
73
+
74
+ PageViewTracking.register_builtin_event!
75
+ track(:page_view)
76
+ end
77
+
78
+ def _track_relay_page_view_allowed?
79
+ condition = self.class._track_relay_page_view_condition
80
+ case condition
81
+ when nil then true
82
+ when Symbol then !!send(condition)
83
+ else !!instance_exec(&condition)
84
+ end
85
+ end
86
+
87
+ # Exactly one page_view per full HTML page render:
88
+ #
89
+ # - GET only (a POST that re-renders a form is not a page view)
90
+ # - successful (2xx) only — redirects land on a page that fires its
91
+ # own page_view; error pages are noise
92
+ # - `text/html` only (excludes JSON and turbo-stream's
93
+ # `text/vnd.turbo-stream.html`)
94
+ # - not a turbo-frame fetch (partial navigation within a page)
95
+ def _track_relay_page_view_response?
96
+ request.get? &&
97
+ response.successful? &&
98
+ response.media_type == "text/html" &&
99
+ request.headers["Turbo-Frame"].nil?
100
+ end
101
+ end
102
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TrackRelay
4
- VERSION = "1.2.0"
4
+ VERSION = "1.4.0"
5
5
  end
data/lib/track_relay.rb CHANGED
@@ -24,6 +24,7 @@ require "track_relay/subscribers/ahoy"
24
24
  require "track_relay/delivery_job"
25
25
  require "track_relay/dispatcher"
26
26
  require "track_relay/controller_tracking"
27
+ require "track_relay/page_view_tracking"
27
28
  require "track_relay/job_tracking"
28
29
  require "track_relay/linter"
29
30
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: track_relay
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 1.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - dchuk
@@ -209,6 +209,7 @@ files:
209
209
  - lib/track_relay/job_tracking.rb
210
210
  - lib/track_relay/linter.rb
211
211
  - lib/track_relay/manifest.rb
212
+ - lib/track_relay/page_view_tracking.rb
212
213
  - lib/track_relay/railtie.rb
213
214
  - lib/track_relay/subscribers/ahoy.rb
214
215
  - lib/track_relay/subscribers/base.rb