1/** 2 * Bridge API utility for storing and retrieving attribution data 3 * across bigissue.com and subscription platform 4 */ 5 6/** 7 * Store attribution data and get a bridge ID 8 * 9 * @param {Object} payload - The attribution data to store 10 * @param {string} payload.content_group - Content category (e.g., 'features', 'news') 11 * @param {string} payload.cta_type - Type of CTA (e.g., 'button', 'link') 12 * @param {string} payload.cta_position - Position of CTA (e.g., 'body', 'header', 'footer') 13 * @param {string} payload.cta_text - Text displayed on the CTA 14 * @param {string} payload.source_page - Full URL of the source page 15 * @param {number} [payload.expires_in=7200] - Time in seconds until data expires (default 2 hours) 16 * @returns {Promise<Object>} Bridge data including bridge_id 17 */ 18async function storeBridgeData(payload) { 19 try { 20 const response = await fetch('/wp-json/bigissue/v1/bridge/store', { 21 method: 'POST', 22 headers: { 23 'Content-Type': 'application/json', 24 }, 25 body: JSON.stringify(payload), 26 }); 27 28 if (!response.ok) { 29 const error = await response.json(); 30 throw new Error(error.message || 'Failed to store bridge data'); 31 } 32 33 const data = await response.json(); 34 return data; 35 } catch (error) { 36 console.error('Bridge API Error:', error); 37 throw error; 38 } 39} 40 41/** 42 * Retrieve attribution data using a bridge ID 43 * 44 * @param {string} bridgeId - The bridge ID to resolve 45 * @returns {Promise<Object>} Bridge data including payload 46 */ 47async function resolveBridgeData(bridgeId) { 48 try { 49 const response = await fetch( 50 `/wp-json/bigissue/v1/bridge/resolve?bridge_id=${encodeURIComponent(bridgeId)}`, 51 { 52 method: 'GET', 53 headers: { 54 'Content-Type': 'application/json', 55 }, 56 } 57 ); 58 59 if (!response.ok) { 60 const error = await response.json(); 61 throw new Error(error.message || 'Failed to resolve bridge data'); 62 } 63 64 const data = await response.json(); 65 return data; 66 } catch (error) { 67 console.error('Bridge API Error:', error); 68 throw error; 69 } 70} 71 72/** 73 * Push attribution to GTM dataLayer (bigissue.com) after a successful bridge store. 74 * Matches subs-side shape under `darwin` where possible for shared GTM variable patterns. 75 * Full-page redirect may drop hits unless GA4 tags use beacon / transport settings in GTM. 76 * 77 * @param {Object} payload - Same fields sent to /bridge/store 78 * @param {string} bridgeId - Returned bridge_id 79 */ 80function pushBridgeDataLayerEvent(payload, bridgeId) { 81 window.dataLayer = window.dataLayer || []; 82 window.dataLayer.push({ 83 event: 'bridge_attribution_stored', 84 darwin: { 85 bridge_id: bridgeId, 86 content_group: payload.content_group, 87 cta_type: payload.cta_type, 88 cta_position: payload.cta_position, 89 cta_text: payload.cta_text, 90 source_page: payload.source_page, 91 }, 92 }); 93} 94 95/** 96 * Store attribution data and redirect to target URL with bridge_id 97 * 98 * @param {string} targetUrl - The URL to redirect to 99 * @param {Object} payload - The attribution data to store (see storeBridgeData) 100 * @returns {Promise<void>} 101 */ 102async function storeAndRedirect(targetUrl, payload) { 103 try { 104 // Store the data and get bridge_id 105 const bridgeData = await storeBridgeData(payload); 106 107 pushBridgeDataLayerEvent(payload, bridgeData.bridge_id); 108 109 // Parse target URL and add bridge_id parameter 110 const url = new URL(targetUrl, window.location.origin); 111 url.searchParams.set('bridge_id', bridgeData.bridge_id); 112 113 // Redirect to target with bridge_id 114 window.location.href = url.toString(); 115 } catch (error) { 116 // If storage fails, redirect without bridge_id 117 console.error('Bridge storage failed, redirecting without bridge_id:', error); 118 window.location.href = targetUrl; 119 } 120} 121 122/** 123 * Helper to attach bridge tracking to a link element 124 * 125 * @param {HTMLElement} element - The link element to attach tracking to 126 * @param {Object} payload - The attribution data to store 127 */ 128function attachBridgeTracking(element, payload) { 129 if (!element || !element.href) { 130 console.warn('Invalid element provided to attachBridgeTracking'); 131 return; 132 } 133 134 element.addEventListener('click', async (e) => { 135 e.preventDefault(); 136 e.stopImmediatePropagation(); // Stop other handlers (like GA) from firing 137 const targetUrl = element.href; 138 await storeAndRedirect(targetUrl, payload); 139 }, true); // Use capture phase to run before GA 140} 141 142/** 143 * Get the content group from page data 144 * Reads from data-article-category attribute on body tag 145 * For single posts, this will be the primary category assigned to the post 146 * Otherwise defaults to 'Unknown' 147 * 148 * @returns {string} 149 */ 150function getContentGroup() { 151 // Read from body tag data attribute (set by WordPress/theme) 152 const body = document.querySelector('body'); 153 const category = body?.getAttribute('data-article-category'); 154 155 return category || 'Unknown'; 156} 157 158/** 159 * Initialize bridge tracking for subscription and shop links 160 * 161 * Automatically detects links to tracked domains and adds attribution tracking 162 * 163 * Tracked domains:
164 * - bigissue.secure.darwin.cx â cta_type defaults to 'Subscription' 165 * - shop.bigissue.dsb-fly.net â cta_type defaults to 'Support' 166 * 167 * Usage in HTML: 168 * <a href="https://bigissue.secure.darwin.cx/subscribe" 169 * data-cta-position="body"> 170 * Subscribe Now 171 * </a> 172 * 173 * Optional attributes: 174 * - data-cta-type: Type of CTA (overrides auto-detection) 175 * - data-cta-position: Position of CTA (default: 'Body') 176 * 177 * Auto-populated: 178 * - cta_text: From link text content 179 * - source_page: Current page URL 180 * - content_group: From WordPress category or 'Unknown' 181 */ 182function initBridgeTracking() { 183 // Define tracked domains and their default CTA types 184 const trackedDomains = [ 185 { domain: 'bigissue.secure.darwin.cx', defaultCtaType: 'Subscription' }, 186 { domain: 'shop.bigissue.dsb-fly.net', defaultCtaType: 'Support' } 187 ]; 188 189 // Find all links to tracked domains 190 trackedDomains.forEach(({ domain, defaultCtaType }) => { 191 const elements = document.querySelectorAll(`a[href*="${domain}"]`); 192 193 elements.forEach((element) => { 194 // Build payload with auto-population and fallbacks 195 const payload = { 196 content_group: getContentGroup(), 197 cta_type: element.dataset.ctaType || defaultCtaType, 198 cta_position: element.dataset.ctaPosition || 'Body', 199 cta_text: element.textContent.trim() || 'Click', 200 source_page: window.location.href, 201 }; 202 203 attachBridgeTracking(element, payload); 204 }); 205 }); 206} 207 208// Auto-initialize on DOM ready 209if (document.readyState === 'loading') { 210 document.addEventListener('DOMContentLoaded', initBridgeTracking); 211} else { 212 // DOM is already loaded 213 initBridgeTracking(); 214} 215 216// Make functions available globally for debugging 217if (typeof window !== 'undefined') { 218 window.bridgeAPI = { 219 init: initBridgeTracking, 220 store: storeBridgeData, 221 resolve: resolveBridgeData, 222 storeAndRedirect: storeAndRedirect 223 }; 224} 225
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.