ga_events 4.0.1 → 4.2.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: '08583d04c842b3ab9c4efa4df756662a83b04dfe3fba2d979dd7d1be3426d173'
4
- data.tar.gz: 1bab40e8a318ca3b540430ab69734960f97dac17a6b250df2b95c5f32378e64d
3
+ metadata.gz: 83fd5b6e15c900730f8f41e03ade3efab0b619a7c4e9e67074db8730ac0701db
4
+ data.tar.gz: e866ec33593b82a6f826004c4c03486203963be5eae9e534ed6be0cad9a3e626
5
5
  SHA512:
6
- metadata.gz: a3a07495ef1e66b621c4bf3e3d8d06ba22add1965003148a53a50ac31bb256ea5faa9519b706c36e568099f0dbf1e4a8929012966b6378c416f58562bfa137b1
7
- data.tar.gz: 066c9032ada9d87d16202bf00fb435b0420a0ae247f1f21cf22bb6ddece372380c2bd5a2bec1532ffdc3d1972b84a084c38b588c48a6a551a3f74f3a925da8c3
6
+ metadata.gz: 0345da75e6b2500c22ef51b72823c3eaf42e71a0e57c9e617119dcacfa4a2aed07ea4120fb25db030d2c3394c322cfce44bdcd44c300ef0deaf275e36b9d5613
7
+ data.tar.gz: d2c2449b6117600eebd172680950a5e42c98f2669c75bb6eda9651d1e1dda0a8da731881b78d205236bedb17130f046a6df500cefd56641667e1c39bc9af35a3
data/.rubocop.yml CHANGED
@@ -1,5 +1,7 @@
1
1
  AllCops:
2
- TargetRubyVersion: 2.3
2
+ SuggestExtensions: false
3
+ NewCops: enable
4
+ TargetRubyVersion: 3.4
3
5
  Exclude:
4
6
  - 'vendor/bundle/**/*'
5
7
  DisplayCopNames: true
data/Gemfile CHANGED
@@ -5,7 +5,6 @@ source 'https://rubygems.org'
5
5
  # Specify your gem's dependencies in ga_events.gemspec
6
6
  gemspec
7
7
 
8
- gem 'pry'
9
8
  gem 'rake'
10
9
  gem 'rspec', '~> 3.12.0'
11
- gem 'rubocop', '~> 0.79.0', require: false
10
+ gem 'rubocop', '~> 1.80.2', require: false
data/README.md CHANGED
@@ -13,9 +13,13 @@ pushes it to Google Analytics via gtag.js or Google Tag Manager.
13
13
 
14
14
  ## Dependencies
15
15
 
16
- * Ruby >= 3.2
17
- * Rails 4.2 onwards
16
+ * Ruby >= 3.4
17
+ * Rails 7.2 onwards
18
+
19
+ ## Optional dependencies
20
+
18
21
  * jQuery
22
+ * Turbolinks or Hotwire Turbo
19
23
 
20
24
  ## Installation
21
25
 
@@ -27,7 +31,7 @@ gem 'ga_events'
27
31
 
28
32
  Run the `bundle` command to install it.
29
33
 
30
- Add to the top of your `application.js` (but after requiring jQuery):
34
+ Add to the top of your `application.js`
31
35
 
32
36
  ```javascript
33
37
  //= require ga_events.js
@@ -16,24 +16,33 @@ class GaEvents.Event
16
16
  # Decompose an event-string (ruby side) into an event object.
17
17
  @from_json: (string) ->
18
18
  events = JSON.parse(string)
19
-
20
- $.map events, (event) =>
19
+ events.map((event) ->
21
20
  if event_name = event.__event__
22
21
  delete event.__event__
23
22
  new @(event_name, event)
23
+ , @)
24
24
 
25
25
  @from_dom: ->
26
26
  data_attribute = "data-#{@html_key}"
27
- dom_events = $("div[#{data_attribute}]").attr data_attribute
28
- @from_json dom_events if dom_events?
27
+ dom_events = document
28
+ .querySelector("div[#{data_attribute}]")
29
+ ?.getAttribute(data_attribute)
30
+
31
+ @from_json(dom_events) if dom_events?
29
32
 
30
33
  # Events should not be sent to an adapter unless the DOM has finished loading.
31
34
  @flush: ->
32
35
  return if @require_user_consent && !@user_consent_given
33
36
 
34
- if @list.length > 0 and @may_flush
35
- $.map @list, (event) -> event.push_to_adapter()
36
- @list = []
37
+ push_events = =>
38
+ if @list.length > 0 and @may_flush
39
+ @list.forEach((event) -> event.push_to_adapter())
40
+ @list = []
41
+
42
+ if window.requestIdleCallback
43
+ requestIdleCallback(push_events, timeout: 250)
44
+ else
45
+ setTimeout(push_events, 1)
37
46
 
38
47
  # Add all events to a queue to flush them later
39
48
  constructor: (@event_name, @options = {}) ->
@@ -49,21 +58,44 @@ class GaEvents.Event
49
58
  # https://support.google.com/analytics/answer/13316687?hl=en#zippy=%2Cweb
50
59
  is_valid_event_name: -> /^[a-z]+[a-z0-9_]*$/i.test(@event_name)
51
60
 
52
- jQuery =>
61
+
62
+ start = ->
53
63
  @may_flush = true
54
64
  @flush()
55
65
 
66
+ addEventListener = document.addEventListener
67
+
56
68
  process_xhr = (xhr) =>
57
69
  xhr_events = xhr.getResponseHeader @header_key
58
70
  @from_json decodeURIComponent(xhr_events) if xhr_events?
59
71
 
60
- $(document).ajaxComplete((_, xhr) -> process_xhr(xhr))
61
- $(document).on "turbolinks:request-end", (event) ->
62
- xhr = event.originalEvent.data.xhr
63
- process_xhr(xhr)
72
+ process_fetch = (response) =>
73
+ events = response.headers.get @header_key
74
+ @from_json decodeURIComponent(events) if events?
75
+
76
+ # jQuery $.ajax
77
+ if window.jQuery && jQuery.ajax
78
+ # This event can only be caught on the jQuery event bus.
79
+ jQuery(document).on("ajaxComplete", (_, xhr) -> process_xhr(xhr))
80
+
81
+
82
+ # classic Turbolinks
83
+ addEventListener("turbolinks:request-end", (event) ->
84
+ process_xhr(event.originalEvent.data.xhr)
85
+ )
86
+
87
+ # Hotwire/Turbo
88
+ addEventListener("turbo:before-fetch-response", (event) ->
89
+ process_fetch(event.detail.fetchResponse.response)
90
+ )
64
91
 
65
92
  @from_dom()
66
93
 
94
+ if document.readyState == "loading"
95
+ addEventListener("DOMContentLoaded", start.bind(@), once: true)
96
+ else
97
+ start.call(@)
98
+
67
99
 
68
100
  class GaEvents.GTagAdapter
69
101
  constructor: (options) ->
data/ga_events.gemspec CHANGED
@@ -20,12 +20,12 @@ Gem::Specification.new do |gem|
20
20
 
21
21
  gem.files = `git ls-files`.split($OUTPUT_RECORD_SEPARATOR)
22
22
  gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
23
- gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
24
23
  gem.name = 'ga_events'
25
24
  gem.require_paths = ['lib']
26
25
  gem.version = GaEvents::VERSION
27
26
  gem.licenses = ['MIT']
28
27
 
29
- gem.required_ruby_version = '>= 3.2'
30
- gem.add_dependency 'rails', '>= 4.2'
28
+ gem.required_ruby_version = '>= 3.4'
29
+ gem.add_dependency 'rails', '>= 7.2'
30
+ gem.metadata['rubygems_mfa_required'] = 'true'
31
31
  end
@@ -9,6 +9,7 @@ module GaEvents
9
9
  module List
10
10
  class << self
11
11
  extend Forwardable
12
+
12
13
  def_delegators :data, :<<, :present?
13
14
 
14
15
  def to_s
@@ -1,10 +1,22 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'rack/utils'
4
+ require 'rack/request'
5
+ require 'rack/headers'
6
+ require 'cgi'
7
+ require 'rubygems'
4
8
 
5
9
  module GaEvents
6
10
  class Middleware
7
11
  SESSION_GA_EVENTS_KEY = 'ga_events.events'
12
+ RESPONSE_HEADER = 'x-ga-events'
13
+
14
+ HEADERS_KLASS = if Gem::Version.new(Rack.release) < Gem::Version.new('3.0')
15
+ Rack::Utils::HeaderHash
16
+ else
17
+ Rack::Headers
18
+ end
19
+ private_constant :HEADERS_KLASS
8
20
 
9
21
  def initialize(app)
10
22
  @app = app
@@ -12,27 +24,27 @@ module GaEvents
12
24
 
13
25
  def call(env)
14
26
  init_event_list(env)
15
- status, headers, response = @app.call(env)
27
+ status, headers, body = @app.call(env)
16
28
 
17
- headers = Rack::Utils::HeaderHash.new(headers)
18
29
  if GaEvents::List.present?
30
+ headers = HEADERS_KLASS.new.merge(headers)
19
31
  request = Rack::Request.new(env)
20
32
 
21
33
  # Can outgrow, headers might get too big
22
- serialized = GaEvents::List.to_s
23
- if xhr_or_turbolinks?(request)
34
+ serialized_events = GaEvents::List.to_s
35
+ if xhr_or_turbo?(request)
24
36
  # AJAX request
25
- headers['x-ga-events'] = CGI.escapeURIComponent(serialized)
37
+ headers[RESPONSE_HEADER] = CGI.escapeURIComponent(serialized_events)
26
38
  elsif redirect?(status)
27
39
  # 30x/redirect? Then add event list to rack session to survive the
28
40
  # redirect.
29
- add_events_to_session(env, serialized)
41
+ add_events_to_session(env, serialized_events)
30
42
  elsif html?(status, headers)
31
- response = inject_div(response, serialized)
43
+ body = inject_div(body, serialized_events)
32
44
  end
33
45
  end
34
46
 
35
- [status, headers, response]
47
+ [status, headers, body]
36
48
  end
37
49
 
38
50
  private
@@ -43,41 +55,42 @@ module GaEvents
43
55
  end
44
56
 
45
57
  def add_events_to_session env, serialized_data
46
- if session = env.dig('rack.session')
58
+ if session = env['rack.session']
47
59
  session[SESSION_GA_EVENTS_KEY] = serialized_data
48
60
  end
49
61
  end
50
62
 
51
- def normalize_response(response)
52
- response = response.body if response.respond_to?(:body)
53
- response = response.join if response.respond_to?(:join)
54
- response
55
- end
56
-
57
- def inject_div(response, serialized_data)
58
- r = normalize_response(response)
59
- serialized_data = CGI.escapeHTML(serialized_data)
60
- [
61
- r.sub('</body>') do |body|
63
+ def inject_div(body, serialized_data)
64
+ if body.respond_to?(:to_ary)
65
+ html = +''
66
+ body.to_ary.each { |chunk| html << chunk }
67
+ html.sub!(%r{</body>}i) do |body|
68
+ serialized_data = CGI.escapeHTML(serialized_data)
62
69
  "<div data-ga-events=\"#{serialized_data}\"></div>#{body}"
63
70
  end
64
- ]
71
+ body = [html]
72
+ end
73
+ body
74
+ ensure
75
+ body.close if body.respond_to?(:close)
65
76
  end
66
77
 
67
78
  # Taken from:
68
79
  # https://github.com/rack/rack-contrib/blob/master/lib/rack/contrib/jsonp.rb
69
- def html?(status, headers)
80
+ def html?(status, response_headers)
70
81
  !Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.include?(status.to_i) &&
71
- headers.key?('Content-Type') &&
72
- headers['Content-Type'].include?('text/html')
82
+ response_headers.key?('content-type') &&
83
+ response_headers['content-type'].start_with?('text/html')
73
84
  end
74
85
 
75
86
  def redirect?(status)
76
87
  (300..399).cover?(status)
77
88
  end
78
89
 
79
- def xhr_or_turbolinks?(request)
80
- request.xhr? || request.env['HTTP_TURBOLINKS_REFERRER']
90
+ def xhr_or_turbo?(request)
91
+ request.xhr? ||
92
+ request.env['HTTP_TURBOLINKS_REFERRER'] ||
93
+ request.env['HTTP_X_TURBO_REQUEST_ID']
81
94
  end
82
95
  end
83
96
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module GaEvents
4
- VERSION = '4.0.1'
4
+ VERSION = '4.2.0'
5
5
  end
data/spec/event_spec.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  RSpec.describe GaEvents::Event do
data/spec/list_spec.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  RSpec.describe 'GaEvents::List' do
@@ -22,7 +24,7 @@ RSpec.describe 'GaEvents::List' do
22
24
  JSON
23
25
 
24
26
  expect(
25
- GaEvents::List.send(:data).to_h { [_1.event_name, _1.event_params] }
27
+ GaEvents::List.send(:data).to_h { [it.event_name, it.event_params] }
26
28
  ).to eq({ 'clicked' => { 'a' => 'a' } })
27
29
  end
28
30
 
@@ -34,7 +36,7 @@ RSpec.describe 'GaEvents::List' do
34
36
  JSON
35
37
 
36
38
  expect(
37
- GaEvents::List.send(:data).to_h { [_1.event_name, _1.event_params] }
39
+ GaEvents::List.send(:data).to_h { [it.event_name, it.event_params] }
38
40
  ).to be_empty
39
41
  end
40
42
  end
@@ -38,12 +38,13 @@ describe GaEvents::Middleware do
38
38
  'cool' => 'stuff',
39
39
  'ding' => ["it's a bug", 'this is "fine"', 'x=1&y=2', '>:3<']
40
40
  )
41
- [200, { 'Content-Type' => 'text/html' }, response_body]
41
+ [200, { 'Content-Type' => 'text/html' }, [response_body]]
42
42
  end
43
43
  end
44
44
 
45
- context 'when no body closing tag exists' do
45
+ context 'when no closing body tag exists' do
46
46
  let(:response) { request.get('/') }
47
+
47
48
  it 'leaves everything as it was' do
48
49
  expect(response.body).to eq response_body
49
50
  end
@@ -60,12 +61,12 @@ describe GaEvents::Middleware do
60
61
  '&quot;__event__&quot;:&quot;test&quot;,' \
61
62
  '&quot;cool&quot;:&quot;stuff&quot;,' \
62
63
  '&quot;ding&quot;:[' \
63
- '&quot;it&#39;s a bug&quot;,' \
64
- '&quot;this is \&quot;fine\&quot;&quot;,' \
65
- '&quot;x=1&amp;y=2&quot;,' \
66
- '&quot;&gt;:3&lt;&quot;' \
64
+ '&quot;it&#39;s a bug&quot;,' \
65
+ '&quot;this is \&quot;fine\&quot;&quot;,' \
66
+ '&quot;x=1&amp;y=2&quot;,' \
67
+ '&quot;&gt;:3&lt;&quot;' \
67
68
  ']' \
68
- '}]"></div></body>'
69
+ '}]"></div></body>'
69
70
  )
70
71
  end
71
72
  end
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe GaEvents::Middleware do
6
+ let(:app) do
7
+ lambda do |env|
8
+ status_code = env.delete('app_status_code').to_i
9
+
10
+ GaEvents::Event.new('test', 'event' => 'stuff')
11
+ [status_code, { 'content-type' => 'text/html' }, [<<~HTML]]
12
+ <!doctype html>
13
+ <html lang=en>
14
+ <head>
15
+ <meta charset=utf-8>
16
+ <title>ABC</title>
17
+ </head>
18
+ <body>
19
+ <p>I'm the content</p>
20
+ </body>
21
+ </html>
22
+ HTML
23
+ end
24
+ end
25
+
26
+ describe 'html requests' do
27
+ it 'adds events in an injected div' do
28
+ response = make_request
29
+ expect(response.body).to include('<div data-ga-events="[{')
30
+ end
31
+
32
+ it 'does no write to rack ression or response headers' do
33
+ session = {}
34
+ response = make_request('rack.session' => session)
35
+ expect(session[GaEvents::Middleware::SESSION_GA_EVENTS_KEY]).to be_nil
36
+ expect(response.headers).not_to have_key(
37
+ GaEvents::Middleware::RESPONSE_HEADER
38
+ )
39
+ end
40
+ end
41
+
42
+ describe 'non xhr/fetch redirects' do
43
+ it 'writes to session' do
44
+ session = {}
45
+ make_request('app_status_code' => '302', 'rack.session' => session)
46
+ expect(session[GaEvents::Middleware::SESSION_GA_EVENTS_KEY]).to be_present
47
+ end
48
+
49
+ it 'does not inject html or add response headers' do
50
+ response = make_request('app_status_code' => '302', 'rack.session' => {})
51
+ expect(response.body).not_to include('<div data-ga-events="[{')
52
+ expect(response.headers).not_to have_key(
53
+ GaEvents::Middleware::RESPONSE_HEADER
54
+ )
55
+ end
56
+ end
57
+
58
+ describe 'xhr requests' do
59
+ it 'adds events as response header' do
60
+ response = make_request('HTTP_X_REQUESTED_WITH' => 'XMLHttpRequest')
61
+ expect(
62
+ response.headers[GaEvents::Middleware::RESPONSE_HEADER]
63
+ ).to include('__event__')
64
+ end
65
+
66
+ it 'does not inject a div or writes to session' do
67
+ session = {}
68
+ response =
69
+ make_request(
70
+ 'HTTP_X_REQUESTED_WITH' => 'XMLHttpRequest',
71
+ 'rack.session' => session
72
+ )
73
+ expect(response.body).not_to include('<div data-ga-events')
74
+ expect(session[GaEvents::Middleware::SESSION_GA_EVENTS_KEY]).to be_nil
75
+ end
76
+ end
77
+
78
+ describe 'turbolink requests' do
79
+ it 'adds events as response header' do
80
+ response = make_request('HTTP_TURBOLINKS_REFERRER' => '...')
81
+ expect(
82
+ response.headers[GaEvents::Middleware::RESPONSE_HEADER]
83
+ ).to include('__event__')
84
+ end
85
+
86
+ it 'does not inject a div with events' do
87
+ response = make_request('HTTP_TURBOLINKS_REFERRER' => '...')
88
+ expect(response.body).not_to include('<div data-ga-events')
89
+ end
90
+ end
91
+
92
+ describe 'turbo requests' do
93
+ it 'adds events as response header' do
94
+ response = make_request('HTTP_X_TURBO_REQUEST_ID' => '...')
95
+ expect(
96
+ response.headers[GaEvents::Middleware::RESPONSE_HEADER]
97
+ ).to include('__event__')
98
+ end
99
+
100
+ it 'does not inject a div with events' do
101
+ response = make_request('HTTP_X_TURBO_REQUEST_ID' => '...')
102
+ expect(response.body).not_to include('<div data-ga-events')
103
+ end
104
+ end
105
+
106
+ private
107
+
108
+ def make_request(options = nil)
109
+ middleware = described_class.new(app)
110
+ options = {
111
+ 'app_status_code' => '200',
112
+ :lint => true,
113
+ :fatal => true,
114
+ **options
115
+ }
116
+ r = Rack::MockRequest.new(middleware)
117
+ r.get('/', options)
118
+ end
119
+ end
data/spec/spec_helper.rb CHANGED
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'bundler/setup'
4
- require 'pry'
5
4
  require 'rspec'
6
5
  require 'rails'
7
6
  require 'ga_events'
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ga_events
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.0.1
4
+ version: 4.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nix-wie-weg Team
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2025-01-08 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: rails
@@ -16,14 +15,14 @@ dependencies:
16
15
  requirements:
17
16
  - - ">="
18
17
  - !ruby/object:Gem::Version
19
- version: '4.2'
18
+ version: '7.2'
20
19
  type: :runtime
21
20
  prerelease: false
22
21
  version_requirements: !ruby/object:Gem::Requirement
23
22
  requirements:
24
23
  - - ">="
25
24
  - !ruby/object:Gem::Version
26
- version: '4.2'
25
+ version: '7.2'
27
26
  description: Google Analytics' Event Tracking everywhere in your Rails app)
28
27
  email:
29
28
  - admin@nix-wie-weg.de
@@ -50,12 +49,13 @@ files:
50
49
  - spec/event_spec.rb
51
50
  - spec/list_spec.rb
52
51
  - spec/middleware_spec.rb
52
+ - spec/middlware_event_placement_spec.rb
53
53
  - spec/spec_helper.rb
54
54
  homepage: https://github.com/Nix-wie-weg/ga_events
55
55
  licenses:
56
56
  - MIT
57
- metadata: {}
58
- post_install_message:
57
+ metadata:
58
+ rubygems_mfa_required: 'true'
59
59
  rdoc_options: []
60
60
  require_paths:
61
61
  - lib
@@ -63,15 +63,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
63
63
  requirements:
64
64
  - - ">="
65
65
  - !ruby/object:Gem::Version
66
- version: '3.2'
66
+ version: '3.4'
67
67
  required_rubygems_version: !ruby/object:Gem::Requirement
68
68
  requirements:
69
69
  - - ">="
70
70
  - !ruby/object:Gem::Version
71
71
  version: '0'
72
72
  requirements: []
73
- rubygems_version: 3.4.6
74
- signing_key:
73
+ rubygems_version: 3.7.2
75
74
  specification_version: 4
76
75
  summary: This gem allows you to annotate events everywhere in the code of your Rails
77
76
  app. A rack middleware is automatically inserted into the stack. It transports the
@@ -79,8 +78,4 @@ summary: This gem allows you to annotate events everywhere in the code of your R
79
78
  a data-pounded custom HTTP header appended. The asset pipeline-ready CoffeeScript
80
79
  extracts this data on the client side and pushes it to Google Analytics via ga.js
81
80
  or Google Tag Manager.
82
- test_files:
83
- - spec/event_spec.rb
84
- - spec/list_spec.rb
85
- - spec/middleware_spec.rb
86
- - spec/spec_helper.rb
81
+ test_files: []