1/** 2 * api/client.js â thin fetch wrapper for the /form-v3/* REST endpoints. 3 * 4 * Auto-attaches the three auth tokens (per plan §4.1): 5 * - `X-WP-Nonce` HTTP header (verifies against `wp_rest`) 6 * - `bp_v3_nonce` body field (verifies against `bookingpress_form_v3_nonce`) 7 * - `bp_v3_instance_token` + `instanceId` body fields (per-instance anti-replay) 8 * 9 * One client per Vue 3 form instance. Caller passes the instance-bound 10 * `state.rest` + `state.nonces` snapshot to `createApiClient()` at mount time. 11 */ 12 13/** 14 * @typedef {Object} ApiConfig 15 * @property {string} restRoot Absolute URL to the `/form-v3/` namespace. 16 * @property {string} instanceId 12-char per-render id. 17 * @property {string} wpRestNonce Nonce against `wp_rest` action. 18 * @property {string} formNonce Nonce against `bookingpress_form_v3_nonce` action. 19 * @property {string} instanceToken Per-render opaque token. 20 */ 21 22/** 23 * @param {ApiConfig} cfg 24 * @returns {object} API methods. 25 */ 26export function createApiClient(cfg) { 27 const root = String(cfg.restRoot || '').replace(/\/+$/, ''); 28 29 async function refreshWpNonce() { 30 try { 31 const refreshPayload = { 32 instanceId: String(cfg.instanceId || ''), 33 }; 34 35 const res = await fetch(`${root}/refresh-nonce`, { 36 method: 'POST', 37 credentials: 'same-origin', 38 cache: 'no-store', 39 headers: { 'Content-Type': 'application/json' }, 40 body: JSON.stringify(refreshPayload), 41 }); 42 const json = await res.json(); 43 if (json && json.ok && json.data && json.data.wp_rest_nonce ) { 44 cfg.wpRestNonce = json.data.wp_rest_nonce; 45 cfg.formNonce = json.data.form_nonce; 46 //cfg.instanceId = json.data.instanceId; 47 cfg.instanceToken = json.data.instanceToken; 48 49 50 return true; 51 } 52 } catch (_err) { /* fall through */ } 53 return false; 54 } 55 56 async function post(route, body, _retried) { 57 const payload = { 58 ...(body || {}), 59 bp_v3_nonce: cfg.formNonce, 60 bp_v3_instance_token: cfg.instanceToken, 61 instanceId: cfg.instanceId, 62 }; 63 if( 'submit' == route ){ 64 if( "undefined" != typeof BookingPressFormV3 ){ 65 var formState = BookingPressFormV3.instances[ cfg.instanceId ].state; 66 if( "undefined" != typeof formState.config.current_locale && formState.config.current_locale ){ 67 payload.locale = formState.config.current_locale; 68 } 69 } 70 } 71 const res = await fetch(`${root}/${route}`, { 72 method: 'POST', 73 credentials: 'same-origin', 74 headers: { 75 'Content-Type': 'application/json', 76 'X-WP-Nonce': cfg.wpRestNonce, 77 }, 78 body: JSON.stringify(payload), 79 }); 80 let json = null; 81 try { 82 json = await res.json(); 83 } catch (_err) { 84 json = { ok: false, error: { code: 'bp_v3_invalid_json', message: 'Server returned non-JSON.' } }; 85 } 86 87 const failureCode = (json && (json.code || (json.error && json.error.code))) || ''; 88 const isStaleAuth = ( 89 failureCode === 'rest_cookie_invalid_nonce' 90 || failureCode === 'bp_v3_rest_invalid_nonce' 91 || failureCode === 'bp_v3_invalid_form_nonce' 92 || failureCode === 'bp_v3_invalid_instance' 93 ); 94 95 if (!res.ok && isStaleAuth && !_retried) { 96 const refreshed = await refreshWpNonce(); 97 if (refreshed) return post(route, body, true); 98 } 99 100 return { status: res.status, ok: !!(json && json.ok), data: json && json.data, error: json && json.error, errors: json && json.errors, raw: json }; 101 } 102 103 return { 104 state(extra) { 105 return post('state', extra || {}); 106 }, 107 timeslots(body) { 108 return post('timeslots', body || {}); 109 }, 110 monthDetails(body) { 111 return post('month-details', body || {}); 112 }, 113 submit(appointmentData) { 114 return post('submit', appointmentData || {}); 115 }, 116 captcha() { 117 return post('captcha', {}); 118 }, 119 validateUsername(username) { 120 return post('validate-username', { username: String(username || '') }); 121 }, 122 paypalValidate(body) { 123 return post('payment/paypal-validate', body || {}); 124 }, 125 paypalConfirm(body) { 126 return post('payment/paypal-confirm', body || {}); 127 }, 128 paypalRedirectPrepare(body) { 129 return post('payment/paypal-redirect-prepare', body || {}); 130 }, 131 }; 132}
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.