From 624853c86888c4f4c0b41cff7be991e60f8948d4 Mon Sep 17 00:00:00 2001 From: Seth Call Date: Fri, 16 Jan 2026 22:15:36 -0600 Subject: [PATCH] Store UTM cookies --- jam-ui/src/helpers/MetaTracking.js | 11 +++++ web/app/assets/javascripts/meta_tracking.js | 46 ++++++++++++------- web/app/controllers/api_recurly_controller.rb | 4 +- web/app/controllers/application_controller.rb | 15 +++++- web/app/controllers/landings_controller.rb | 4 ++ web/app/controllers/sessions_controller.rb | 9 +++- 6 files changed, 68 insertions(+), 21 deletions(-) diff --git a/jam-ui/src/helpers/MetaTracking.js b/jam-ui/src/helpers/MetaTracking.js index 27720b803..046119065 100644 --- a/jam-ui/src/helpers/MetaTracking.js +++ b/jam-ui/src/helpers/MetaTracking.js @@ -12,6 +12,7 @@ const MetaTracking = { const location = window.location; this.handleFbc(location.search); this.handleFbp(); + this.handleUtm(location.search); }, // 1. Parsing and storing _fbc (Click ID) @@ -30,6 +31,16 @@ const MetaTracking = { } }, + handleUtm: function (searchParams) { + const utmParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content']; + utmParams.forEach(param => { + const value = this.getQueryParam(param, searchParams); + if (value) { + this.setCookie(param, value, 90); + } + }); + }, + // 2. Handling _fbp (Browser ID) handleFbp: function () { if (!this.getCookie('_fbp')) { diff --git a/web/app/assets/javascripts/meta_tracking.js b/web/app/assets/javascripts/meta_tracking.js index 764c839e2..08bb0b508 100644 --- a/web/app/assets/javascripts/meta_tracking.js +++ b/web/app/assets/javascripts/meta_tracking.js @@ -7,49 +7,63 @@ * - Checks for `_fbp` cookie; if missing, generates and sets it. */ -(function(window, document) { +(function (window, document) { 'use strict'; var MetaTracking = { - init: function() { + init: function () { var location = window.location; this.handleFbc(location.search); this.handleFbp(); + this.handleUtm(location.search); }, // 1. Parsing and storing _fbc (Click ID) - handleFbc: function(searchParams) { + handleFbc: function (searchParams) { var fbclid = this.getQueryParam('fbclid', searchParams); - + if (fbclid) { var version = 'fb'; var subdomainIndex = 1; // 1 = example.com var creationTime = new Date().getTime(); // Unix timestamp in ms - + // Format: fb.1.timestamp.id var fbcValue = version + '.' + subdomainIndex + '.' + creationTime + '.' + fbclid; - + this.setCookie('_fbc', fbcValue, 90); } }, + handleUtm: function (searchParams) { + var utmParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content']; + var self = this; + // forEach not supported in IE8, but this is modern enough or we can use for loop + for (var i = 0; i < utmParams.length; i++) { + var param = utmParams[i]; + var value = self.getQueryParam(param, searchParams); + if (value) { + self.setCookie(param, value, 90); + } + } + }, + // 2. Handling _fbp (Browser ID) - handleFbp: function() { + handleFbp: function () { if (!this.getCookie('_fbp')) { var version = 'fb'; var subdomainIndex = 1; var creationTime = new Date().getTime(); var randomInt = Math.floor(Math.random() * 10000000000); // 10-digit random number - + // Format: fb.1.timestamp.randomDigits var fbpValue = version + '.' + subdomainIndex + '.' + creationTime + '.' + randomInt; - + this.setCookie('_fbp', fbpValue, 90); } }, // Helper: Get query param by name - getQueryParam: function(name, search) { + getQueryParam: function (name, search) { name = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]'); var regex = new RegExp('[\\?&]' + name + '=([^&#]*)'); var results = regex.exec(search); @@ -57,7 +71,7 @@ }, // Helper: Set cookie - setCookie: function(name, value, days) { + setCookie: function (name, value, days) { var expires = ""; if (days) { var date = new Date(); @@ -69,13 +83,13 @@ }, // Helper: Get cookie - getCookie: function(name) { + getCookie: function (name) { var nameEQ = name + "="; var ca = document.cookie.split(';'); - for(var i=0;i < ca.length;i++) { + for (var i = 0; i < ca.length; i++) { var c = ca[i]; - while (c.charAt(0) == ' ') c = c.substring(1,c.length); - if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length); + while (c.charAt(0) == ' ') c = c.substring(1, c.length); + if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length); } return null; } @@ -86,7 +100,7 @@ MetaTracking.init(); } else { // IE9+ support for DOMContentLoaded - document.addEventListener('DOMContentLoaded', function() { + document.addEventListener('DOMContentLoaded', function () { MetaTracking.init(); }); } diff --git a/web/app/controllers/api_recurly_controller.rb b/web/app/controllers/api_recurly_controller.rb index 392aca3aa..cc82ac4e1 100644 --- a/web/app/controllers/api_recurly_controller.rb +++ b/web/app/controllers/api_recurly_controller.rb @@ -41,7 +41,9 @@ class ApiRecurlyController < ApiController reuse_card: reuse_card_next_time, affiliate_referral_id: cookies[:affiliate_visitor], origin: origin_cookie, - timezone: current_timezone + timezone: current_timezone, + facebook_click_id: cookies[:_fbc], + facebook_browser_id: cookies[:_fbp] } options = User.musician_defaults(request.remote_ip, ApplicationHelper.base_uri(request) + "/confirm", any_user, options) diff --git a/web/app/controllers/application_controller.rb b/web/app/controllers/application_controller.rb index d092e08e2..31ef9c635 100644 --- a/web/app/controllers/application_controller.rb +++ b/web/app/controllers/application_controller.rb @@ -49,11 +49,22 @@ class ApplicationController < ActionController::Base def origin_cookie begin - JSON.parse(cookies[:origin]) if cookies[:origin] + data = JSON.parse(cookies[:origin]) if cookies[:origin] rescue - nil + data = nil end + # Backfill with individual UTM cookies if present + # This supports cases where the frontend (jam-ui/web) set specific cookies + # or if the JSON cookie is missing/incomplete. + %w(utm_source utm_medium utm_campaign utm_term utm_content).each do |key| + if cookies[key].present? + data ||= {} + data[key] = cookies[key] + end + end + + data end def track_origin diff --git a/web/app/controllers/landings_controller.rb b/web/app/controllers/landings_controller.rb index a2d5b8076..ae11d82a5 100644 --- a/web/app/controllers/landings_controller.rb +++ b/web/app/controllers/landings_controller.rb @@ -635,6 +635,10 @@ class LandingsController < ApplicationController musician: true, timezone: current_timezone, first_name: @first, + origin: origin_cookie, + affiliate_referral_id: cookies[:affiliate_visitor], + facebook_click_id: cookies[:_fbc], + facebook_browser_id: cookies[:_fbp], instruments: [{:instrument_id => @instrument, :proficiency_level => 1, :priority => 1}]) if @user.errors.any? first = @user.errors.first diff --git a/web/app/controllers/sessions_controller.rb b/web/app/controllers/sessions_controller.rb index d3c5460bc..fc1439eb0 100644 --- a/web/app/controllers/sessions_controller.rb +++ b/web/app/controllers/sessions_controller.rb @@ -120,7 +120,10 @@ class SessionsController < ApplicationController last_name: auth_hash[:info][:last_name], email: auth_hash[:info][:email], timezone: current_timezone, - affiliate_referral_id: cookies[:affiliate_visitor]) + affiliate_referral_id: cookies[:affiliate_visitor], + origin: origin_cookie, + facebook_click_id: cookies[:_fbc], + facebook_browser_id: cookies[:_fbp]) # Users who sign up using oauth are presumed to have valid email adddresses. user.confirm_email! @@ -196,7 +199,9 @@ class SessionsController < ApplicationController location: {:country => nil, :state => nil, :city => nil}, affiliate_referral_id: cookies[:affiliate_visitor], origin: origin_cookie, - timezone: current_timezone + timezone: current_timezone, + facebook_click_id: cookies[:_fbc], + facebook_browser_id: cookies[:_fbp] } options = User.musician_defaults(request.remote_ip, ApplicationHelper.base_uri(request) + "/confirm", any_user, options)