1/** 2 * @typedef {Object} PSU_Subscribe_v3 3 * @property {Object} classes 4 * @property {typeof ContactIdHandler} classes.ContactIdHandler 5 */ 6 7/** 8 * ContactIdHandler 9 * Handles contact ID operations including fetch, set, and analytics integration 10 */ 11 12/** 13 * @typedef {Object} jQuery 14 * @property {(settings: jQueryAjaxSettings) => Promise<any>} ajax 15 */ 16 17/** 18 * @typedef {Object} jQueryAjaxSettings 19 * @property {string} url 20 * @property {string} method 21 * @property {Object} data 22 */ 23 24/** 25 * @external PSU_Subscribe_v3_ContactIdHandler_JSConfig 26 * @global 27 * @property {string} ajaxUrl WordPress AJAX URL 28 * @property {string} nonce Security nonce 29 */ 30class PSU_Subscribe_v3_ContactIdHandler { 31 constructor(contactIdHandlerConfig) { 32 33 if (!contactIdHandlerConfig?.ajaxUrl || !contactIdHandlerConfig?.nonce) { 34 throw new Error('[PSU_Subscribe_v3_ContactIdHandler] Missing required configuration'); 35 } 36 37 // Load configuration 38 this.contactIdHandlerConfig = contactIdHandlerConfig; 39 40 // Core state 41 this.initializationComplete = false; 42 this.cookieHandler = window.psuCoreCookieHandler; 43 44 // Contact ID state 45 this.contactIdNeededEmailEncoded = null; 46 this.currentAttempt = 0; 47 this.maxAttempts = 5; 48 this.retryDelay = 3000; 49 50 // Initialize immediately but don't block 51 this.initialize().catch(error => { 52 console.error('[PSU_Subscribe_v3_ContactIdHandler] Initialization failed:', error); 53 }); 54 } 55 56 /** 57 * Initialize handler operations 58 * @returns {Promise<void>} 59 */ 60 async initialize() { 61 if (this.initializationComplete) return; 62 63 try { 64 // Wait for tracker to be ready using shared function 65 const isReady = await psuAnalyticsV1getTrackerWhenReady('PSU_Subscribe_v3_ContactIdHandler'); 66 if (!isReady) { 67 console.warn('[PSU_Subscribe_v3_ContactIdHandler] Analytics tracker not available'); 68 return; 69 } 70 71 await this.maybeHandleContactID(); 72 this.initializationComplete = true; 73 } catch (error) { 74 console.error('[PSU_Subscribe_v3_ContactIdHandler] Initialization error:', error); 75 this.initializationComplete = false; 76 } 77 } 78 79 /** 80 * Process contact ID operations 81 * @returns {Promise<void>} 82 */ 83 async maybeHandleContactID() { 84 try { 85 // First check URL parameters 86 if (await this.maybeSetContactIdFromUrl()) { 87 return; 88 } 89 90 // Then check for existing contact ID 91 const existingId = this.getStoredContactId(); 92 if (existingId) { 93 console.log('[PSU_Subscribe_v3_ContactIdHandler] Existing contact ID found:', existingId); 94 this.sendContactIdToGoogleAnalytics(existingId); 95 return; 96 } 97 98 // Finally check if we need to fetch a contact 99 const contactIdNeededData = this.cookieHandler.getCookie('CONTACT_ID_NEEDED'); 100 if (contactIdNeededData?.email_encoded) { 101 this.contactIdNeededEmailEncoded = contactIdNeededData.email_encoded; 102 await this.fetchContactId(); 103 } 104 105 } catch (error) { 106 console.error('[PSU_Subscribe_v3_ContactIdHandler] Error handling contact ID:', error); 107 throw error; 108 } 109 } 110 111 /** 112 * Check URL for contact ID parameter 113 * @returns {Promise<boolean>} 114 */ 115 async maybeSetContactIdFromUrl() { 116 const urlParams = new URLSearchParams(window.location.search); 117 const contactId = urlParams.get('psu_contact_id'); 118 119 if (contactId) { 120 await this.handleSetContactIdViaUrl(contactId); 121 return true; 122 } 123 124 return false; 125 } 126 127 /** 128 * Set contact ID and emit tracking event 129 * @param {string} contactId GetResponse contact ID to store and track 130 * @returns {Promise<void>} 131 */ 132 async handleSetContactIdViaUrl(contactId) { 133 if (!contactId) return; 134 135 this.dispatchSetContactIdViaUrlEvent(contactId); 136 this.saveContactIdToCookie(contactId); 137 this.sendContactIdToGoogleAnalytics(contactId); 138 } 139 140 /** 141 * Emit contact tracking event 142 */ 143 dispatchSetContactIdViaUrlEvent(contactId) { 144 if (!contactId) return; 145 146 const event = new CustomEvent('psu_subscribe_v3_contact_id_set_via_url', { 147 detail: { 148 contactId, 149 timestamp: new Date().toISOString() 150 }, 151 bubbles: true 152 }); 153 154 document.dispatchEvent(event); 155 } 156 157 /** 158 * Fetch contact ID from server with retry logic 159 * @returns {Promise<void>} 160 */ 161 async fetchContactId() { 162 if (this.currentAttempt >= this.maxAttempts) { 163 console.warn('[PSU_Subscribe_v3_ContactIdHandler] Max contact ID fetch attempts reached'); 164 return; 165 } 166 167 console.log('[PSU_Subscribe_v3_ContactIdHandler] Fetching contact ID...'); 168 169 try { 170 const response = await jQuery.ajax({ 171 url: this.contactIdHandlerConfig.ajaxUrl, 172 method: 'POST', 173 data: { 174 action: 'psu_fetch_contact_id', 175 nonce: this.contactIdHandlerConfig.nonce, 176 email_encoded: this.contactIdNeededEmailEncoded 177 } 178 }); 179 180 if (response.success && response.data.contact_id) { 181 await this.handleContactIdFetched(response.data.contact_id); 182 this.clearContactIdNeeded(); 183 } else { 184 await this.scheduleRetry(); 185 } 186 } catch (error) { 187 console.error('[PSU_Subscribe_v3_ContactIdHandler] Error fetching contact ID:', error); 188 await this.scheduleRetry(); 189 } 190 } 191 192 /** 193 * Handle fetched contact ID 194 * @param {string} contactId GetResponse contact ID to store and track 195 * @returns {Promise<void>} 196 */ 197 async handleContactIdFetched(contactId) { 198 if (!contactId) return; 199 200 console.log('[PSU_Subscribe_v3_ContactIdHandler] Contact ID fetched:', contactId); 201 202 this.dispatchContactIdFetchedEvent(contactId); 203 this.saveContactIdToCookie(contactId); 204 this.sendContactIdToGoogleAnalytics(contactId); 205 } 206 207 /** 208 * Emit contact tracking event 209 */ 210 dispatchContactIdFetchedEvent(contactId) { 211 if (!contactId) return; 212 213 const event = new CustomEvent('psu_subscribe_v3_contact_id_fetched', { 214 detail: { 215 contactId, 216 email_encoded: this.contactIdNeededEmailEncoded, 217 timestamp: new Date().toISOString() 218 }, 219 bubbles: true 220 }); 221 222 document.dispatchEvent(event); 223 } 224 225 /** 226 * Store contact ID in cookie 227 * @param {string} contactId 228 */ 229 saveContactIdToCookie(contactId) { 230 if (!contactId) return; 231 this.cookieHandler.setCookie('CONTACT_ID', {contact_id: contactId}); 232 } 233 234 235 /** 236 * Set user ID in GA4 237 * @param {string} contactId GetResponse contact ID to set as user ID 238 */ 239 sendContactIdToGoogleAnalytics(contactId) { 240 if (!contactId) return; 241 242 try { 243 if (typeof gtag === 'function') { 244 gtag('set', 'user_id', contactId); 245 console.log('[PSU_Subscribe_v3_ContactIdHandler] GA4 user_id set:', contactId); 246 } else { 247 console.warn('[PSU_Subscribe_v3_ContactIdHandler] gtag not available'); 248 } 249 } catch (error) { 250 console.error('[PSU_Subscribe_v3_ContactIdHandler] Failed to set user ID:', error); 251 } 252 } 253 254 /** 255 * Clear contact ID needed cookie 256 */ 257 clearContactIdNeeded() { 258 this.cookieHandler.deleteCookie('CONTACT_ID_NEEDED'); 259 } 260 261 /** 262 * Get stored contact ID from cookie 263 * @returns {string|null} 264 */ 265 getStoredContactId() { 266 const data = this.cookieHandler.getCookie('CONTACT_ID'); 267 return data?.contact_id || null; 268 } 269 270 /** 271 * Schedule retry attempt with exponential backoff 272 * @returns {Promise<void>} 273 */ 274 async scheduleRetry() { 275 this.currentAttempt++; 276 const backoffDelay = this.retryDelay * Math.pow(1.5, this.currentAttempt - 1); 277 278 return new Promise(resolve => { 279 setTimeout(() => { 280 this.fetchContactId().then(resolve); 281 }, backoffDelay); 282 }); 283 } 284} 285 286/** @type {PSU_Subscribe_v3_ContactIdHandler_JSConfig} */
287window.PSU_Subscribe_v3_ContactIdHandler_JSConfig = window.PSU_Subscribe_v3_ContactIdHandler_JSConfig??{}; 288 289// Initialize when document is ready 290jQuery(document).ready(() => { 291 if (typeof window.PSU_Subscribe_v3_ContactIdHandler_JSConfig === 'undefined') { 292 console.error('[PSU_Subscribe_v3_ContactIdHandler] Missing PSU_Subscribe_v3_ContactIdHandler_JSConfig'); 293 } else { 294 PSU_Subscribe_v3.contactIdHandler = new PSU_Subscribe_v3_ContactIdHandler( 295 window.PSU_Subscribe_v3_ContactIdHandler_JSConfig 296 ); 297 } 298});
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.