1/** 2 * SectionStore â per-section SWR store backed by localStorage. 3 * 4 * Loaded globally via script_footer.php in the order api.js -> section.js -> 5 * init.js, so it runs on every page after PAApi is available and before init.js 6 * consumes it. Designed to hydrate per-visitor data (cart / favorite / customer) 7 * out of the FPC-cached HTML: the page renders instantly from cache and this 8 * store revalidates each section in the background. 9 * 10 * On load it auto-fires one background sync (via PAApi.syncSections) for the 11 * registered sections (cart / favorite / customer). The HTTP call itself lives 12 * in api.js; this module only owns storage, subscriptions, and cross-tab relay. 13 * 14 * Storage layout: 15 * localStorage["pa_section_<name>"] = JSON.stringify({ version, data }) 16 * 17 * Server contract (POST /rest/web/sections, issued by PAApi.syncSections): 18 * request: { sections: ["cart","favorite","customer"], versions: { cart: "v", ... } } 19 * response: { sections: { "<changed>": { version, data }, ... } } 20 * A section absent from the response means "not modified" â the local copy is kept. 21 * 22 * Cross-tab: writing localStorage in one tab fires a `storage` event in OTHER 23 * tabs (not the writing tab), so those tabs re-fire onUpdated without hitting 24 * the server. The writing tab fires onUpdated explicitly inside writeLocal(). 25 */ 26window.SectionStore = (function () { 27 var KEY_PREFIX = 'pa_section_'; 28 29 // name -> array of callbacks 30 var listeners = {}; 31 // sections this store syncs on every page load. SectionStore loads before 32 // init.js and auto-fires one sync for these on construction. 33 var registered = ['cart', 'favorite', 'customer']; 34 // single in-flight sync promise 35 var inFlight = null; 36 37 function key(name) { return KEY_PREFIX + name; } 38 39 function readLocal(name) { 40 try { 41 var raw = localStorage.getItem(key(name)); 42 if (!raw) return null; 43 var parsed = JSON.parse(raw); 44 if (!parsed || typeof parsed !== 'object') return null; 45 return parsed; // { version, data } 46 } catch (e) { 47 return null; 48 } 49 } 50 51 function writeLocal(name, version, data) { 52 try { 53 localStorage.setItem(key(name), JSON.stringify({ version: version, data: data })); 54 } catch (e) { /* quota / disabled â ignore */ } 55 fire(name, data); 56 } 57 58 function localVersion(name) { 59 var entry = readLocal(name); 60 return entry ? (entry.version || null) : null; 61 } 62 63 function fire(name, data) { 64 var cbs = listeners[name]; 65 if (!cbs) return; 66 cbs.slice().forEach(function (cb) { 67 try { cb(data); } catch (e) { /* swallow listener errors */ } 68 }); 69 } 70 71 /** 72 * GET a section's data. Returns the cached value synchronously (stale-while- 73 * revalidate). Returns null on first ever load (no cache yet); a background 74 * sync is kicked so onUpdated will fire once fresh data arrives. 75 */ 76 function get(name) { 77 var entry = readLocal(name); 78 kickSync(); 79 return entry ? entry.data : null; 80 } 81 82 /** 83 * Subscribe to per-section updates. Returns an unsubscribe function. 84 */ 85 function onUpdated(name, cb) { 86 if (typeof cb !== 'function') return function () {}; 87 if (!listeners[name]) listeners[name] = []; 88 listeners[name].push(cb); 89 return function () { offUpdated(name, cb); }; 90 } 91 92 function offUpdated(name, cb) { 93 var cbs = listeners[name]; 94 if (!cbs) return; 95 var i = cbs.indexOf(cb); 96 if (i !== -1) cbs.splice(i, 1); 97 } 98 99 /** 100 * Force the next sync to re-fetch this section (drops the stored version, 101 * keeps the data so get() still returns something). Call from a same-session 102 * write endpoint (e.g. add-to-cart) so the next page load revalidates 103 * immediately, regardless of CDC lag. 104 */
105 function invalidate(name) { 106 var entry = readLocal(name); 107 if (entry) { 108 try { 109 localStorage.setItem(key(name), JSON.stringify({ version: null, data: entry.data })); 110 } catch (e) { /* ignore */ } 111 } 112 kickSync([name]); 113 } 114 115 /** 116 * Write a section directly from a write-endpoint response (no server round 117 * trip). Optional version; if omitted the local version is cleared so the 118 * next sync still revalidates against the server. 119 */ 120 function setLocal(name, data, version) { 121 writeLocal(name, (typeof version === 'undefined' ? null : version), data); 122 } 123 124 /** 125 * Drop the cached cart badge to empty immediately after checkout. Server-side 126 * checkout clears the cart and bumps the section version, but localStorage can 127 * still hold the pre-order count until the next sync â which FPC pages surface 128 * instantly via mini-cart.js. Call before redirecting to confirm_order (or 129 * upsell hops that precede it) so the next page does not flash a stale qty. 130 */ 131 function resetCartAfterCheckout() { 132 writeLocal('cart', null, { count: 0, cart_item_qty: 0, subtotal: '0.00', items: [] }); 133 kickSync(['cart']); 134 } 135 136 /** 137 * Single-flight sync. If names is omitted, syncs all registered sections. 138 * Concurrent calls share one in-flight request. 139 */ 140 function sync(names) { 141 return kickSync(names); 142 } 143 144 function kickSync(forceNames) { 145 if (inFlight) return inFlight; 146 var names = (forceNames && forceNames.length) ? forceNames.slice() : registered.slice(); 147 if (!names.length) return Promise.resolve({ sections: {} }); 148 149 var versions = {}; 150 names.forEach(function (n) { versions[n] = localVersion(n); }); 151 152 // The HTTP call lives in api.js (PAApi.syncSections) so timeout / abort / 153 // error normalization are shared with the rest of the client. api.js is 154 // loaded before section.js, so PAApi is guaranteed to exist here. 155 inFlight = window.PAApi.syncSections(names, versions).then(function (res) { 156 inFlight = null; 157 var changed = (res && res.sections) || {}; 158 Object.keys(changed).forEach(function (n) { 159 var payload = changed[n]; 160 if (payload && typeof payload === 'object' && 'data' in payload) { 161 writeLocal(n, payload.version || null, payload.data); 162 } 163 }); 164 return res; 165 }).catch(function () { 166 inFlight = null; 167 return { sections: {} }; 168 }); 169 return inFlight; 170 } 171 172 /** 173 * Register sections to sync on every page load. Idempotent. 174 */ 175 function register(names) { 176 names.forEach(function (n) { 177 if (registered.indexOf(n) === -1) registered.push(n); 178 }); 179 } 180 181 // Cross-tab: another tab wrote a section's localStorage -> re-fire here. 182 if (typeof window !== 'undefined') { 183 window.addEventListener('storage', function (e) { 184 if (!e || !e.key || e.key.indexOf(KEY_PREFIX) !== 0) return; 185 var name = e.key.slice(KEY_PREFIX.length); 186 var entry = readLocal(name); 187 if (entry) fire(name, entry.data); 188 }); 189 } 190 191 // Auto-sync on load: SectionStore is loaded before init.js, so fire one 192 // background sync for the registered sections immediately. It does not 193 // block â callers read localStorage via get() (stale-while-revalidate) and 194 // onUpdated fires when fresh data lands. 195 kickSync(); 196 197 return { 198 get: get, 199 sync: sync, 200 register: register, 201 onUpdated: onUpdated, 202 offUpdated: offUpdated, 203 invalidate: invalidate, 204 setLocal: setLocal, 205 resetCartAfterCheckout: resetCartAfterCheckout, 206 }; 207})();
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.