1/** 2 * AI Chatbot â WooCommerce Cart Bridge 3 * 4 * Runs on the WordPress parent page (same origin as WooCommerce). 5 * Listens for postMessage requests from the chatbot iframe (agents.robofy.ai) 6 * and proxies WooCommerce Store API cart calls back, forwarding the WC nonce 7 * and session cookies that are inaccessible from the cross-origin iframe. 8 * 9 * Incoming message types (from iframe): 10 * { type: 'wc_bridge_ping' } 11 * { type: 'wc_cart_request', requestId: '<id>', action: 'get_cart' } 12 * { type: 'wc_cart_request', requestId: '<id>', action: 'add_item', payload: { product_id, quantity, variation } } 13 * { type: 'wc_cart_request', requestId: '<id>', action: 'remove_item', payload: { cart_item_key } } 14 * 15 * Outgoing message types (to iframe): 16 * { type: 'wc_bridge_ready', nonce: '<nonce>' } 17 * { type: 'wc_cart_response', requestId: '<id>', success: true, data: { ... } } 18 * { type: 'wc_cart_response', requestId: '<id>', success: false, error: '<message>' } 19 */ 20( function () { 21 'use strict'; 22 23 // Config is injected by PHP via wp_localize_script. 24 var config = window.aiChatbotWooBridge; 25 if ( ! config || ! config.iframeOrigin || ! config.nonce || ! config.storeApiUrl ) { 26 return; 27 } 28 29 var iframeOrigin = config.iframeOrigin; // e.g. 'https://agents.robofy.ai' 30 var nonce = config.nonce; 31 var storeApiUrl = config.storeApiUrl.replace( /\/$/, '' ); // e.g. 'https://example.com/wp-json/wc/store/v1' 32 33 /** 34 * Build standard headers for every WC Store API request. 35 */ 36 function buildHeaders() { 37 return { 38 'Content-Type' : 'application/json', 39 'Nonce' : nonce, 40 'X-WC-Store-API-Nonce': nonce 41 }; 42 } 43 44 /** 45 * Send a postMessage back to the iframe. 46 * 47 * @param {Window} source The iframe's contentWindow (event.source). 48 * @param {Object} payload The message object to send. 49 */ 50 function reply( source, payload ) { 51 if ( source && typeof source.postMessage === 'function' ) { 52 source.postMessage( payload, iframeOrigin ); 53 } 54 } 55 56 /** 57 * Parse an error message out of a WC Store API error response. 58 * 59 * @param {Response} response The fetch Response object. 60 * @return {Promise<string>} 61 */ 62 function parseErrorMessage( response ) { 63 return response.json() 64 .then( function ( body ) { 65 return ( body && body.message ) ? body.message : response.statusText; 66 } ) 67 .catch( function () { 68 return response.statusText || 'Unknown error'; 69 } ); 70 } 71 72 /** 73 * Handle a wc_cart_request message from the iframe. 74 * 75 * @param {Window} source event.source 76 * @param {string} requestId Caller-supplied correlation ID. 77 * @param {string} action 'get_cart' | 'add_item' | 'remove_item' 78 * @param {Object} payload Action-specific parameters. 79 */ 80 function handleCartRequest( source, requestId, action, payload ) { 81 var fetchOptions; 82 83 if ( action === 'get_cart' ) { 84 fetchOptions = { 85 method : 'GET', 86 headers : buildHeaders(), 87 credentials: 'include' 88 }; 89 90 fetch( storeApiUrl + '/cart', fetchOptions ) 91 .then( function ( response ) { 92 if ( response.ok ) { 93 return response.json().then( function ( data ) { 94 reply( source, { type: 'wc_cart_response', requestId: requestId, success: true, data: data } ); 95 } ); 96 } 97 return parseErrorMessage( response ).then( function ( msg ) { 98 reply( source, { type: 'wc_cart_response', requestId: requestId, success: false, error: msg } ); 99 } ); 100 } ) 101 .catch( function () { 102 reply( source, { type: 'wc_cart_response', requestId: requestId, success: false, error: 'Network error' } ); 103 } ); 104 105 } else if ( action === 'add_item' ) { 106 var addBody = { 107 id : payload && payload.product_id ? payload.product_id : 0, 108 quantity : payload && payload.quantity ? payload.quantity : 1, 109 variation: payload && payload.variation ? payload.variation : [] 110 }; 111 112 fetchOptions = { 113 method : 'POST', 114 headers : buildHeaders(), 115 credentials: 'include', 116 body : JSON.stringify( addBody ) 117 }; 118 119 fetch( storeApiUrl + '/cart/add-item', fetchOptions ) 120 .then( function ( response ) { 121 if ( response.ok ) { 122 return response.json().then( function ( data ) { 123 reply( source, { type: 'wc_cart_response', requestId: requestId, success: true, data: data } ); 124 } ); 125 } 126 return parseErrorMessage( response ).then( function ( msg ) { 127 reply( source, { type: 'wc_cart_response', requestId: requestId, success: false, error: msg } ); 128 } ); 129 } ) 130 .catch( function () { 131 reply( source, { type: 'wc_cart_response', requestId: requestId, success: false, error: 'Network error' } ); 132 } ); 133 134 } else if ( action === 'remove_item' ) { 135 var removeBody = { 136 key: payload && payload.cart_item_key ? payload.cart_item_key : '' 137 }; 138 139 fetchOptions = { 140 method : 'POST', 141 headers : buildHeaders(), 142 credentials: 'include', 143 body : JSON.stringify( removeBody ) 144 }; 145 146 fetch( storeApiUrl + '/cart/remove-item', fetchOptions ) 147 .then( function ( response ) { 148 if ( response.ok ) { 149 return response.json().then( function ( data ) { 150 reply( source, { type: 'wc_cart_response', requestId: requestId, success: true, data: data } ); 151 } ); 152 } 153 return parseErrorMessage( response ).then( function ( msg ) { 154 reply( source, { type: 'wc_cart_response', requestId: requestId, success: false, error: msg } ); 155 } ); 156 } ) 157 .catch( function () { 158 reply( source, { type: 'wc_cart_response', requestId: requestId, success: false, error: 'Network error' } ); 159 } ); 160 161 } else { 162 reply( source, { type: 'wc_cart_response', requestId: requestId, success: false, error: 'Unknown action: ' + action } ); 163 } 164 } 165
166 /** 167 * Central message listener. 168 */ 169 function onMessage( event ) { 170 // Security: only accept messages from the trusted iframe origin. 171 if ( event.origin !== iframeOrigin ) { 172 return; 173 } 174 175 var data = event.data; 176 if ( ! data || typeof data !== 'object' ) { 177 return; 178 } 179 180 if ( data.type === 'wc_bridge_ping' ) { 181 // Handshake â let the iframe know the bridge is present and share the nonce. 182 reply( event.source, { type: 'wc_bridge_ready', nonce: nonce } ); 183 return; 184 } 185 186 if ( data.type === 'wc_cart_request' ) { 187 handleCartRequest( 188 event.source, 189 data.requestId || '', 190 data.action || '', 191 data.payload || {} 192 ); 193 } 194 } 195 196 // Attach listener. DOMContentLoaded may have already fired if the script is 197 // in the footer (in_footer = true), so use a readyState guard. 198 if ( document.readyState === 'loading' ) { 199 document.addEventListener( 'DOMContentLoaded', function () { 200 window.addEventListener( 'message', onMessage ); 201 } ); 202 } else { 203 window.addEventListener( 'message', onMessage ); 204 } 205 206}() );
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.