1(function () { 2 "use strict"; 3 4 const API = "https://api.ceyapt.com"; 5 const STORAGE_KEY = "reservationData"; 6 const TIMEOUT_MS = 12000; 7 let epoch = 0; 8 let refreshFlight = null; 9 let loginFlight = null; 10 let watching = false; 11 let sessionNotice = null; 12 13 function isItalian() { 14 const main = document.querySelector("main[data-language]"); 15 const language = main && main.getAttribute("data-language"); 16 if (language === "it" || language === "en") return language === "it"; 17 return /\/your-reservation(?:-login)?-it(?:\/|$)/i.test(window.location.pathname || ""); 18 } 19 20 function words(english, italian) { return isItalian() ? italian : english; } 21 function loginPath() { return "/your-reservation-login" + (isItalian() ? "-it" : "") + "/"; } 22 function guestPath() { return "/your-reservation" + (isItalian() ? "-it" : "") + "/"; } 23 function isGuestPage() { return !!document.querySelector("main.reservation-page"); } 24 function failure(code, status) { return Object.assign(new Error(code), { code: code, status: status || 0 }); } 25 26 function storedText() { 27 try { return window.sessionStorage.getItem(STORAGE_KEY); } 28 catch (_) { return null; } 29 } 30 31 function validGuest(value) { 32 return !!value && typeof value === "object" && !Array.isArray(value) && 33 value.success === true && value.app === "roma" && value.role === "guest" && 34 (typeof value.guestId === "string" || typeof value.guestId === "number") && 35 String(value.guestId).trim() !== "" && typeof value.structure === "string" && 36 typeof value.authToken === "string" && value.authToken.length > 0 && 37 value.authToken.length <= 16000 && !/\s/.test(value.authToken); 38 } 39 40 function read() { 41 try { 42 const value = JSON.parse(storedText()); 43 return validGuest(value) ? value : null; 44 } catch (_) { return null; } 45 } 46 47 function save(guest) { 48 try { window.sessionStorage.setItem(STORAGE_KEY, JSON.stringify(guest)); } 49 catch (_) { throw failure("session_storage_unavailable"); } 50 } 51 52 function clear() { 53 try { window.sessionStorage.removeItem(STORAGE_KEY); } 54 catch (_) { /* A blocked storage area contains no readable usable session. */ } 55 } 56 57 function sameSession(snapshot) { 58 const current = read(); 59 return epoch === snapshot.epoch && !!current && 60 String(current.guestId) === snapshot.guestId && current.authToken === snapshot.token; 61 } 62 63 function cancelPending() { 64 if (refreshFlight) refreshFlight.controller.abort(); 65 if (loginFlight) loginFlight.controller.abort(); 66 refreshFlight = null; 67 loginFlight = null; 68 } 69 70 function expireSession(snapshot) { 71 if (snapshot && !sameSession(snapshot)) return; 72 epoch += 1; 73 clear(); 74 cancelPending(); 75 if (isGuestPage()) window.location.replace(loginPath()); 76 } 77 78 async function requestJSON(path, options, controller) { 79 let timedOut = false; 80 const timer = window.setTimeout(function () { 81 timedOut = true; 82 controller.abort(); 83 }, TIMEOUT_MS); 84 try { 85 const response = await window.fetch(API + path, Object.assign({ 86 credentials: "omit", cache: "no-store", signal: controller.signal 87 }, options)); 88 if (!response.ok) throw failure("session_http_error", response.status); 89 let data; 90 try { data = await response.json(); } 91 catch (_) { throw failure("invalid_session_response"); } 92 return data; 93 } catch (error) { 94 if (timedOut) throw failure("session_timeout"); 95 if (controller.signal.aborted) throw failure("session_superseded"); 96 if (error.code) throw error; 97 throw failure("session_connection_error"); 98 } finally { window.clearTimeout(timer); } 99 } 100 101 function notice(message, busy) { 102 if (!isGuestPage()) return; 103 if (!sessionNotice && !message) return; 104 if (!sessionNotice) { 105 const main = document.querySelector("main.reservation-page"); 106 const box = document.createElement("div"); 107 box.id = "cyReservationSessionStatus"; 108 box.className = "reservation-container cy-session-status"; 109 box.setAttribute("role", "status"); 110 box.setAttribute("aria-live", "polite"); 111 box.style.padding = "16px"; 112 box.style.background = "#f6f3ee"; 113 box.style.color = "#716551"; 114 const messageNode = document.createElement("span"); 115 const retry = document.createElement("button"); 116 retry.type = "button"; 117 retry.textContent = words("Retry", "Riprova"); 118 retry.style.marginLeft = "12px"; 119 retry.addEventListener("click", function () { 120 notice(words("Updating your reservationâ¦", "Aggiornamento della prenotazioneâ¦"), true); 121 refresh().catch(function () {}); 122 }); 123 box.appendChild(messageNode); 124 box.appendChild(retry); 125 main.insertBefore(box, main.firstChild); 126 sessionNotice = { box: box, message: messageNode, retry: retry }; 127 } 128 sessionNotice.box.hidden = !message; 129 sessionNotice.message.textContent = message || ""; 130 sessionNotice.retry.disabled = !!busy; 131 } 132 133 function showRefreshError(error) { 134 if (error.code === "session_superseded" || error.status === 401 || error.status === 403) return; 135 const message = error.code === "session_storage_unavailable" ? 136 words("Your browser could not save the updated reservation. Allow site data and retry.", 137 "Il browser non riesce a salvare la prenotazione aggiornata. Consenti i dati del sito e riprova.") : 138 words("We could not update your reservation. The details shown may be out of date. Please retry.", 139 "Non è stato possibile aggiornare la prenotazione. I dati mostrati potrebbero non essere aggiornati. Riprova."); 140 notice(message, false); 141 } 142 143 function refresh() { 144 const guest = read(); 145 if (!guest) { 146 expireSession(); 147 return Promise.reject(failure("guest_session_required", 401)); 148 } 149 const snapshot = { epoch: epoch, guestId: String(guest.guestId), token: guest.authToken }; 150 if (refreshFlight && refreshFlight.snapshot.epoch === snapshot.epoch && 151 refreshFlight.snapshot.guestId === snapshot.guestId && refreshFlight.snapshot.token === snapshot.token) { 152 return refreshFlight.promise; 153 } 154 if (refreshFlight) refreshFlight.controller.abort(); 155 const flight = { snapshot: snapshot, controller: new AbortController(), promise: null }; 156 refreshFlight = flight; 157 flight.promise = (async function () { 158 try { 159 const updated = await requestJSON("/roma/auth/session", { 160 method: "GET", headers: { "Authorization": "Bearer " + snapshot.token, "Accept": "application/json" } 161 }, flight.controller); 162 // A reply for an earlier login must never restore a logged-out or replaced session. 163 if (!sameSession(snapshot)) throw failure("session_superseded"); 164 if (!validGuest(updated) || String(updated.guestId) !== snapshot.guestId || 165 updated.structure.trim().toLowerCase() !== guest.structure.trim().toLowerCase()) { 166 throw failure("invalid_session_response"); 167 } 168 const current = read(); 169 // Another authenticated module may have received a newer admin revision meanwhile. 170 const currentRevision = Number(current.guestRevision) || 0; 171 const incomingRevision = Number(updated.guestRevision) || 0; 172 const result = incomingRevision < currentRevision ? 173 Object.assign({}, current, { authToken: updated.authToken }) : updated; 174 save(result); 175 notice("", false); 176 window.dispatchEvent(new CustomEvent("cy:reservation-updated", { detail: result })); 177 return result; 178 } catch (error) { 179 if (!sameSession(snapshot)) throw failure("session_superseded"); 180 if (error.status === 401 || error.status === 403) expireSession(snapshot); 181 else showRefreshError(error); 182 throw error; 183 } finally { 184 if (refreshFlight === flight) refreshFlight = null; 185 } 186 }()); 187 return flight.promise; 188 } 189 190 function paintGuest(guest) { 191 if (!guest) return; 192 const values = {
193 guestName: [guest.name, guest.surname].filter(Boolean).join(" "), 194 guestStructure: "C&Y Roma " + (guest.structure || ""), 195 checkIn: guest.checkIn, checkOut: guest.checkOut, inTime: guest.inTime, outTime: guest.outTime 196 }; 197 Object.keys(values).forEach(function (id) { 198 const element = document.getElementById(id); 199 if (element) element.textContent = values[id] || "â"; 200 }); 201 const pictures = { 202 trastevere: ["trastevereterrace1.jpg", "trasteverehousemap.png"], 203 vaticano: ["vaticano1.jpg", "vaticanohousemap.png"], 204 prati: ["prati1.jpg", "pratihousemap.png"] 205 }; 206 const propertyKey = String(guest.structure || "").trim().toLowerCase(); 207 const pair = Object.prototype.hasOwnProperty.call(pictures, propertyKey) ? pictures[propertyKey] : null; 208 const hero = document.getElementById("reservationHero"); 209 const map = document.getElementById("houseMapImage"); 210 const prefix = "/wp-content/uploads/2026/02/"; 211 if (hero) hero.style.backgroundImage = pair ? "url('" + prefix + pair[0] + "')" : ""; 212 if (map) { 213 if (pair) map.src = prefix + pair[1]; 214 else map.removeAttribute("src"); 215 map.hidden = !pair; 216 const section = map.closest(".house-map-section"); 217 if (section) section.hidden = !pair; 218 } 219 } 220 221 function backgroundRefresh() { 222 if (isGuestPage() && !document.hidden) refresh().catch(function () {}); 223 } 224 225 function watchGuestPage() { 226 if (watching || !isGuestPage()) return; 227 watching = true; 228 window.addEventListener("focus", backgroundRefresh); 229 document.addEventListener("visibilitychange", backgroundRefresh); 230 window.setInterval(backgroundRefresh, 60000); 231 } 232 233 function loadReservationPage() { 234 if (!isGuestPage()) return Promise.resolve(null); 235 const guest = read(); 236 if (!guest) { 237 expireSession(); 238 return Promise.resolve(null); 239 } 240 paintGuest(guest); 241 watchGuestPage(); 242 // The public page initializer is safe for existing callers that do not await it. 243 return refresh().catch(function () { return null; }); 244 } 245 246 function loginError(error) { 247 if (error.status === 401 || error.status === 400) return words( 248 "Check your booking number and the last four digits of your phone number.", 249 "Controlla il numero di prenotazione e le ultime quattro cifre del telefono."); 250 if (error.status === 403) return words( 251 "This reservation is not currently available for access. Please contact C&Y for assistance.", 252 "Lâaccesso non è disponibile per questa prenotazione. Contatta C&Y per assistenza."); 253 if (error.code === "session_storage_unavailable") return words( 254 "Your browser could not save the session. Allow site data and retry.", 255 "Il browser non riesce a salvare la sessione. Consenti i dati del sito e riprova."); 256 if (error.code === "session_timeout") return words( 257 "The server took too long to respond. Please retry.", "Il server non ha risposto in tempo. Riprova."); 258 return words("We could not connect to your reservation. Please retry.", 259 "Non è stato possibile accedere alla prenotazione. Riprova."); 260 } 261 262 function reservationLogin() { 263 if (loginFlight) return loginFlight.promise; 264 const form = document.getElementById("reservationLoginForm"); 265 const bookingInput = document.getElementById("booking"); 266 const phoneInput = document.getElementById("phone"); 267 const errorNode = document.getElementById("errorMsg"); 268 if (!form || !bookingInput || !phoneInput || !errorNode) return Promise.resolve(null); 269 const booking = bookingInput.value.trim(); 270 const phone = phoneInput.value.trim(); 271 errorNode.textContent = ""; 272 if (!booking || !/^\d{4}$/.test(phone)) { 273 errorNode.textContent = words("Enter your booking number and the last four digits of your phone number.", 274 "Inserisci il numero di prenotazione e le ultime quattro cifre del telefono."); 275 (booking ? phoneInput : bookingInput).focus(); 276 return Promise.resolve(null); 277 } 278 epoch += 1; 279 if (refreshFlight) refreshFlight.controller.abort(); 280 refreshFlight = null; 281 const originalStorage = storedText(); 282 const flight = { epoch: epoch, controller: new AbortController(), promise: null }; 283 const button = form.querySelector("button[type='submit']"); 284 const previousLabel = button ? button.textContent : ""; 285 if (button) { 286 button.disabled = true; 287 button.textContent = words("Signing inâ¦", "Accesso in corsoâ¦"); 288 } 289 form.setAttribute("aria-busy", "true"); 290 loginFlight = flight; 291 flight.promise = (async function () { 292 try { 293 const guest = await requestJSON("/roma/auth/login", { 294 method: "POST", headers: { "Content-Type": "application/json", "Accept": "application/json" }, 295 body: JSON.stringify({ username: booking, password: phone }) 296 }, flight.controller); 297 if (epoch !== flight.epoch || storedText() !== originalStorage) throw failure("session_superseded"); 298 if (!validGuest(guest)) throw failure("invalid_session_response"); 299 save(guest); 300 epoch += 1; 301 window.location.assign(guestPath()); 302 return guest; 303 } catch (error) { 304 if (epoch === flight.epoch && storedText() === originalStorage && error.code !== "session_superseded") { 305 errorNode.textContent = loginError(error); 306 } 307 return null; 308 } finally { 309 if (loginFlight === flight) { 310 loginFlight = null; 311 form.removeAttribute("aria-busy"); 312 if (button) { button.disabled = false; button.textContent = previousLabel; } 313 } 314 } 315 }()); 316 return flight.promise; 317 } 318 319 function logoutReservation() { 320 epoch += 1; 321 cancelPending(); 322 clear(); 323 window.location.replace(loginPath()); 324 } 325 326 window.cyReservationSession = { read: read, refresh: refresh }; 327 window.loadReservationPage = loadReservationPage; 328 window.reservationLogin = reservationLogin; 329 window.logoutReservation = logoutReservation; 330 window.addEventListener("cy:reservation-updated", function (event) { 331 const current = read(); 332 if (current && event.detail && String(current.guestId) === String(event.detail.guestId) && 333 current.authToken === event.detail.authToken) paintGuest(current); 334 }); 335
336 function init() { 337 const form = document.getElementById("reservationLoginForm"); 338 if (form && !form.hasAttribute("data-session-bound")) { 339 form.setAttribute("data-session-bound", "true"); 340 form.addEventListener("submit", function (event) { event.preventDefault(); reservationLogin(); }); 341 const button = form.querySelector("button[type='submit']"); 342 if (button) button.disabled = false; 343 } 344 if (isGuestPage()) loadReservationPage(); 345 } 346 if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", init, { once: true }); 347 else init(); 348}());
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.