PageSourceSearch

https://www.backsplash.com/wp-content/plugins/retainful/assets/store/js/retainful-store.js?ver=1.0.9

js backsplash.com collected 2026-09-25 23:40:45 UTC 14,771 bytes, 346 lines download raw bytes

1if (typeof (rtfl_jquery) === 'undefined') {
2    rtfl_jquery = jQuery.noConflict();
3}
4
5rtfl = window.rtfl || {};
6
7(function (rtfl) {
8
9    let Url = retainful_localize_data?.ajax_url ?? '';
10    /**
11     * Validate email address
12     * @param {string} value - Email address to validate
13     * @returns {boolean} - True if valid, false otherwise
14     */
15    function validateEmail(value) {
16        var valid = true;
17        if (!value || value.trim() === '') {
18            return false;
19        }
20        if (value.indexOf('@') === -1) {
21            valid = false;
22        } else {
23            var parts = value.split('@');
24            if (parts.length !== 2 || !parts[0] || !parts[1]) {
25                valid = false;
26            } else {
27                var domain = parts[1];
28                if (domain.indexOf('.') === -1) {
29                    valid = false;
30                } else {
31                    var domainParts = domain.split('.');
32                    var ext = domainParts[domainParts.length - 1];
33                    if (!ext || ext.length > 14 || ext.length < 2) {
34                        valid = false;
35                    }
36                }
37            }
38        }
39        return valid;
40    }
41
42    /**
43     * Validate phone number
44     * @param {string} value - Phone number to validate
45     * @returns {boolean} - True if valid, false otherwise
46     */
47    function validatePhone(value) {
48        if (!value || value.trim() === '') {
49            return false;
50        }
51        // Remove common phone number characters for validation
52        var cleaned = value.replace(/[\s\-\(\)\+]/g, '');
53        // Check if it contains only digits and has reasonable length (at least 7 digits, max 15)
54        return /^\d+$/.test(cleaned) && cleaned.length >= 7 && cleaned.length <= 15;
55    }
56
57    /**
58     * Update checkout and track user data
59     * @param {string} email - User email
60     * @param {string} phone - User phone
61     * @param {object} guestData - Guest data object
62     */
63    function updateCheckout(email, phone, guestData) {
64        if (!Url) {
65            return;
66        }
67
68        // Validate email if provided
69        if (email && email.trim() !== '' && !validateEmail(email)) {
70            // Invalid email, don't send request
71            return;
72        }
73
74        if ( email && validateEmail( email ) ) {
75            var previous = sessionStorage.getItem( 'rtfl_customer_email' );
76            sessionStorage.setItem( 'rtfl_customer_email', email );
77            // Notify product-view tracker that a customer was just identified
78            if ( previous !== email ) {
79                rtfl_jquery( document ).trigger( 'rtfl:customer_identified', [ email ] );
80            }
81        }
82
83        // Validate phone if provided
84        if (phone && phone.trim() !== '' && !validatePhone(phone)) {
85            // Invalid phone, don't send request
86            return;
87        }
88
89        // Add nonce to the request data
90        if (typeof retainful_localize_data !== 'undefined' && retainful_localize_data.nonce) {
91            guestData.nonce = retainful_localize_data.nonce;
92        }
93
94        rtfl_jquery.ajax({
95            url: Url,
96            type: "POST",
97            dataType: 'json',
98            data: guestData,
99            success: function(response) {
100                // Cart tracking updated successfully
101                if (response && response.success) {
102                    // Optionally trigger cart sync after user data is updated
103                }
104            },
105            error: function(xhr, status, error) {
106                // Handle error silently or log if needed
107                console.error('Retainful: Failed to track user data', error);
108            }
109        });
110    }
111
112    // Track cart when checkout email field updates (Traditional WooCommerce Checkout)
113    // Support both old selector (rnoc_allow_gdpr) and new selector (rtfl_allow_gdpr) for backward compatibility
114    rtfl_jquery(document).on('change', 'input#billing_email,input#billing_first_name,input#billing_last_name,input#billing_phone,input#rtfl_allow_gdpr,input#rnoc_allow_gdpr,input#rtfl_sms_consent', function () {
115        var rnoc_phone = rtfl_jquery("#billing_phone").val();
116        var rnoc_email = rtfl_jquery("#billing_email").val();
117        var ship_to_bill = rtfl_jquery("#ship-to-different-address-checkbox:checked").length;
118
119        // Get marketing consent checkbox value (support both old and new selectors)
120        var marketing_consent_checkbox = rtfl_jquery('input#rtfl_allow_gdpr');
121        if (marketing_consent_checkbox.length === 0) {
122            marketing_consent_checkbox = rtfl_jquery('input#rnoc_allow_gdpr');
123        }
124        var allow_gdpr = marketing_consent_checkbox.is(':checked');
125
126        // Get SMS consent checkbox value
127        var sms_consent_checkbox = rtfl_jquery('input#rtfl_sms_consent');
128        var sms_consent = sms_consent_checkbox.is(':checked');
129        var guest_data = {
130            billing_first_name: rtfl_jquery('#billing_first_name').val(),
131            billing_last_name: rtfl_jquery('#billing_last_name').val(),
132            billing_company: rtfl_jquery('#billing_company').val(),
133            billing_address_1: rtfl_jquery('#billing_address_1').val(),
134            billing_address_2: rtfl_jquery('#billing_address_2').val(),
135            billing_city: rtfl_jquery('#billing_city').val(),
136            billing_state: rtfl_jquery('#billing_state').val(),
137            billing_postcode: rtfl_jquery('#billing_postcode').val(),
138            billing_country: rtfl_jquery('#billing_country').val(),
139            billing_phone: rtfl_jquery('#billing_phone').val() || rtfl_jquery('input[type="tel"]').val(),
140            billing_email: rtfl_jquery('#billing_email').val(),
141            ship_to_billing: ship_to_bill,
142            order_notes: rtfl_jquery('#order_comments').val(),
143            shipping_first_name: rtfl_jquery('#shipping_first_name').val(),
144            shipping_last_name: rtfl_jquery('#shipping_last_name').val(),
145            shipping_company: rtfl_jquery('#shipping_company').val(),
146            shipping_address_1: rtfl_jquery('#shipping_address_1').val(),
147            shipping_address_2: rtfl_jquery('#shipping_address_2').val(),
148            shipping_city: rtfl_jquery('#shipping_city').val(),
149            shipping_state: rtfl_jquery('#shipping_state').val(),
150            shipping_postcode: rtfl_jquery('#shipping_postcode').val(),
151            shipping_country: rtfl_jquery('#shipping_country').val(),
152            allow_gdpr: allow_gdpr,
153            sms_consent: sms_consent,
154            cart_token: localStorage.getItem('retainful_ac_cart_token'),
155            action: 'rnoc_track_user_data'
156        };
157
158        updateCheckout(rnoc_email, rnoc_phone, guest_data);
159    });
160
161    // Track cart when checkout email field updates (WooCommerce Blocks Checkout)
162    // Block Checkout uses name attributes with namespace format: rtfl--allow_gdpr or data attributes
163    // We need to listen for multiple possible selectors to catch all cases
164    rtfl_jquery(document).on('change', '.wp-block-woocommerce-checkout input[type="email"],.wp-block-woocommerce-checkout input[type="tel"],.wp-block-woocommerce-checkout input[name*="rtfl"],.wp-block-woocommerce-checkout input[name*="rnoc"],.wp-block-woocommerce-checkout input[id*="rtfl"],.wp-block-woocommerce-checkout input[id*="rnoc"]', function () {
165        
166        var rnoc_email = rtfl_jquery(".wp-block-woocommerce-checkout input[type='email']").first().val();
167        var rnoc_phone = rtfl_jquery(".wp-block-woocommerce-checkout input[type='tel']").first().val();
168
169        // Get marketing consent checkbox value
170        // Block Checkout converts rtfl/allow_gdpr to name="rtfl--allow_gdpr" or similar format
171        var marketing_consent_checkbox =
172        rtfl_jquery('.wp-block-woocommerce-checkout #contact-rtfl-allow_gdpr').length
173            ? rtfl_jquery('.wp-block-woocommerce-checkout #contact-rtfl-allow_gdpr')
174            : rtfl_jquery('.wp-block-woocommerce-checkout #order-rtfl-allow_gdpr');
175        
176         var allow_gdpr = marketing_consent_checkbox.is(':checked');
177
178        // Get SMS consent checkbox value
179        // Block Checkout converts rtfl/sms_consent to name="rtfl--sms_consent" or similar format
180        var sms_consent_checkbox =
181        rtfl_jquery('.wp-block-woocommerce-checkout #billing-rtfl-sms_consent').length
182            ? rtfl_jquery('.wp-block-woocommerce-checkout #billing-rtfl-sms_consent')
183            : rtfl_jquery('.wp-block-woocommerce-checkout #order-rtfl-sms_consent');
184
185        var sms_consent = sms_consent_checkbox.is(':checked');
186
187
188        var guest_data = {
189            billing_first_name: rtfl_jquery('.wp-block-woocommerce-checkout #billing-first_name').first().val() || '',
190            billing_last_name: rtfl_jquery('.wp-block-woocommerce-checkout #billing-last_name').first().val() || '',
191            billing_address_1: rtfl_jquery('.wp-block-woocommerce-checkout #billing-address_1').first().val() || '',
192            billing_address_2: rtfl_jquery('.wp-block-woocommerce-checkout #billing-address_2').first().val() || '',
193            billing_city: rtfl_jquery('.wp-block-woocommerce-checkout #billing-city').first().val() || '',
194            billing_postcode: rtfl_jquery('.wp-block-woocommerce-checkout #billing-postcode').first().val() || '',
195            billing_phone: rnoc_phone || '',
196            billing_email: rnoc_email || '',
197            allow_gdpr: allow_gdpr,
198            sms_consent: sms_consent,
199            ship_to_billing: 1,
200            cart_token: localStorage.getItem('retainful_ac_cart_token'),
201            action: 'rnoc_track_user_data'
202        };
203
204        updateCheckout(rnoc_email, rnoc_phone, guest_data);
205    });
206
207    rtfl_jquery(document).on("retainful-form-submitted", function (e, email) {
208        if (e.email === undefined) {
209            return;
210        }
211        var guest_data = {
212            action: 'rnoc_track_user_data',
213            billing_email: e.email
214        };
215        updateCheckout(e.email, '', guest_data);
216    });
217
218
219    rtfl_jquery(document).ready(function(){
220        maybeTrack();
221        // This runs when a product page is viewed
222        /*if (typeof retainful_localize_data !== 'undefined' &&
223            retainful_localize_data.viewed_product &&
224            retainful_localize_data.viewed_product.product_id) {
225
226            var productData = retainful_localize_data.viewed_product;
227            var productId = productData.product_id;
228            var quantity = productData.quantity || 0;
229
230            window.wc_product = {
231                id: productId,
232                quantity: quantity
233            };
234
235        
236        }*/
237    });
238    var trackingConfig = (typeof retainful_localize_data !== 'undefined')
239    ? retainful_localize_data.viewed_product_tracking
240    : null;
241
242    if ( !trackingConfig || !trackingConfig.enabled ) return;
243
244    var viewedProduct = (typeof retainful_localize_data !== 'undefined')
245        ? retainful_localize_data.viewed_product
246    : null;
247
248    if ( !viewedProduct || !viewedProduct.product_id ) return;
249
250    var productId    = parseInt( viewedProduct.product_id, 10 );
251    var dedupMinutes = parseInt( trackingConfig.dedup_window || 30, 10 );
252
253    /**
254     * Get identified email: PHP-injected (logged in / WC session) OR
255     * stored by the checkout field tracker in sessionStorage.
256     */
257    function getIdentifiedEmail() {
258        // PHP-injected email (logged-in user or existing WC session)
259        if ( trackingConfig.customer_email && trackingConfig.customer_email !== '' ) {
260            return trackingConfig.customer_email;
261        }
262        // Written by the checkout field onChange handler when guest types email
263        return sessionStorage.getItem( 'rtfl_customer_email' ) || '';
264    }
265
266    function isProductTrackEnabled() {
267        return trackingConfig && trackingConfig.enabled;
268    }
269
270    /**
271     * Check if this product view was already sent within the dedup window.
272     * Key: rtfl_pv_{email_hash}_{product_id}
273     * Value: timestamp of last send
274     */
275    function isDuplicate( email, productId ) {
276        var key  = 'rtfl_pv_' + hashString( email ) + '_' + productId;
277        var last = sessionStorage.getItem( key );
278        if ( !last ) return false;
279        var elapsed = ( Date.now() - parseInt( last, 10 ) ) / 1000 / 60; // minutes
280        return elapsed < dedupMinutes;
281    }
282
283    function markAsSent( email, productId ) {
284        var key = 'rtfl_pv_' + hashString( email ) + '_' + productId;
285        sessionStorage.setItem( key, Date.now().toString() );
286    }
287
288    /** Tiny non-cryptographic hash — just enough for a sessionStorage key */
289    function hashString( str ) {
290        var h = 0;
291        for ( var i = 0; i < str.length; i++ ) {
292            h = ( Math.imul( 31, h ) + str.charCodeAt( i ) ) | 0;
293        }
294        return Math.abs( h ).toString( 36 );
295    }
296
297    function sendViewedProductEvent( email ) {
298        if ( !Url || !email ) return;
299        var isEnabled = isProductTrackEnabled();
300        if( !isEnabled ) return;
301        rtfl_jquery.ajax({
302            url:      Url,
303            type:     'POST',
304            dataType: 'json',
305            data: {
306                action:     'rtfl_track_product_viewed',
307                nonce:      retainful_localize_data.nonce,
308                product_id: productId,
309                email:      email,
310            },
311            success: function ( response ) {
312                if ( response && response.success ) {
313                    markAsSent( email, productId );
314                }
315            },
316            error: function () {
317                // Silent — non-critical tracking event
318            }
319        });
320    }
321
322    /**
323     * Main entry: fire once on page load if customer is identified.
324     * If not yet identified (anonymous visitor), listen for the checkout
325     * field tracker to capture the email and fire then.
326     */
327    function maybeTrack() {
328        var isEnabled = isProductTrackEnabled();
329        if( !isEnabled ) return;
330        var email = getIdentifiedEmail();
331        if ( email && !isDuplicate( email, productId ) ) {
332            if(typeof productId == 'undefined') return;
333            sendViewedProductEvent( email );
334            return;
335        }
336
337        // Anonymous visitor — wait for email identification from checkout tracker.
338        // The checkout onChange handler stores email in sessionStorage as
339        // 'rtfl_customer_email'; we listen for a custom event it dispatches.
340        /*rtfl_jquery( document ).one( 'rtfl:customer_identified', function ( e, identifiedEmail ) {
341            if ( identifiedEmail && !isDuplicate( identifiedEmail, productId ) ) {
342                sendViewedProductEvent( identifiedEmail );
343            }
344        });*/
345    }
346})(rtfl_jquery);

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.