const SELFBOOK_BASE_URL="https://sdk.selfbook.com",SELFBOOK_BACKEND_URL="https://api.selfbook.com/api/v2",SELFBOOK_SCRIPT_ID="selfbook_jssdk",SELFBOOK_HOTEL_ID_PROP="hotelId",SELFBOOK_API_KEY_PROP="apiKey",SELFBOOK_WIDGET_ELEMENT_ID="selfbook_sdkwidget",SELFBOOK_WIDGET_WRAPPER_ELEMENT_ID="selfbook_sdkwidget_wrapper",SELFBOOK_WIDGET_LOADING_ELEMENT="selfbook-loading",SELFBOOK_WIDGET_RESUME="selfbook-sdkwidget-resume",SELFBOOK_WIDGET_RESUME_TEXT="selfbook-sdkwidget-resume-text",BOOTSTRAP_ACTION="WIDGET/BOOTSTRAP",SELFBOOK_HOTEL_GROUP_INFO="selfbook_hotel_group_info",BLACK_SQUARE_THEME_HOTELS=["6739"],THE_LINE_HOTELS=["68711","71661","2427"],COMPLETE_BOOKING_BTN_CLICK="Complete your reservation button click",ROUTE_PATHS={app:{confirmation:"/confirmation",wallet:"/wallet",account:"/account",settings:"/settings",reservations:"/reservations",reservationDetail:"/reservation-detail",editBooking:"/edit-booking",bookingConfirmed:"/booking-confirmed"}},INITIAL_PERSISTED_DATA={isExpired:!0,all:null,booking:null,core:null,router:null},GUEST_TYPE={ADULT:"adult",CHILD:"child",INFANT:"infants"};function checkStatus(t){if(t.status>=200&&t.status<300)return t;const e=new Error(t.statusText);throw e.response=t,e}async function parseJSON(t){try{const e=await t.text();if(!e)return;return JSON.parse(e)}catch(t){console.error("parseJSON: err: ",t)}}function intervalWrapper(t,e){const n=setInterval(t,e);return function(){clearInterval(n)}}function getHotelBasicData(t,e){try{const n=getFromLocalStorage(t,SELFBOOK_HOTEL_GROUP_INFO).hotels?.filter((t=>t.id==e));return n?.[0]||{}}catch(t){return console.error("getHotelBasicData: err: ",t),{}}}function getFromLocalStorage(t,e){try{return JSON.parse(t.getItem(e))}catch(t){return console.error("getFromLocalStorage: err: ",{e:t}),{}}}function getPersistedData(t){try{const e=JSON.parse(t.getItem("persist:root"));if(!e)return INITIAL_PERSISTED_DATA;const n=JSON.parse(e.booking||"{}"),o=JSON.parse(e.core||"{}"),r=JSON.parse(e?.router||"{}"),{expireTime:a}=o,s=(Date.now()-new Date(o.interactionTime))/1e3;return{isExpired:s>a,all:e,booking:n,core:o,router:r}}catch(t){return console.error("getPersistedData: err: ",t),INITIAL_PERSISTED_DATA}}function fillGuestsFields(t=[],e){const n=e.default_adult_occupancy?e.default_adult_occupancy:2;return t.map((t=>t.type===GUEST_TYPE.ADULT?{type:GUEST_TYPE.ADULT,count:parseInt(t.count)||n}:t.type===GUEST_TYPE.CHILD&&parseInt(t.count)>0?{type:GUEST_TYPE.CHILD,count:parseInt(t.count),age:parseInt(t.age)||1,special_request:t.special_request||""}:t.type===GUEST_TYPE.INFANT&&parseInt(t.count)>0?{type:GUEST_TYPE.INFANT,count:parseInt(t.count)||1,age:parseInt(t.age)||1,special_request:t.special_request||""}:void 0)).filter(Boolean)}function buildRedirectSynxisLink(t,e){let n=`https://be.synxis.com/?hotel=${e.id}&theme=${e.synxis_theme}&config=${e.synxis_config}`;try{if(t){const{startDate:e,endDate:o,promoCode:r,groupCode:a,guests:s,iataNumber:i,couponCode:l,destinationId:d,nights:c,rate:p,hotelId:u,roomCategory:m}=t;c&&(n+=`&nights=${c}`),e&&(n+=`&arrive=${e}`),o&&(n+=`&depart=${o}`),r&&(n+=`&promo=${r}`),a&&(n+=`&group=${a}`),i&&(n+=`&iataNumber=${i}`),l&&(n+=`&couponCode=${l}`),p&&(n+=`&rate=${p}`),d&&(n+=`&destinationId=${d}`),Array.isArray(m)?n+=`&roomCategory=${m.map((t=>t)).join(",")}`:m&&(n+=`&roomCategory=${m}`),s&&s.length>0?("adult"===s[0].type&&(n+=`&adult=${s[0].count}`),s[1]&&"child"===s[1].type&&(n+=`&child=${s[1].count}`)):n+="&adult=1"}}catch(t){console.error("buildRedirectSynxisLink: err: ",t)}return n}function selectShowWidgetButtonCopies(t){return t.core.showWidgetButtonCopies||{}}function selectRoute(t){return t?.router?.location?t.router.location.pathname:ROUTE_PATHS.app.editBooking}function isObjectEqual(t,e){return JSON.stringify(t)===JSON.stringify(e)}function isTemplateDomainMatch(t,e){return t?.booking_template_domain&&e?.target?.href?.includes(t.booking_template_domain)}function getElementById(t){return document?.getElementById(t)}function runDirectApplication(){console.info("(!) selfbook: widget initialization started");let t,e,n,o,r,a=!1,s=!1,i=!1,l=!1;async function d({url:t,method:e,data:n,headers:o}){try{const r=await fetch(t,{method:e,body:n,headers:o});checkStatus(r);return await parseJSON(r)}catch(t){console.debug("requestWrapper: Request failed",t)}}function c(){try{const t=n||{},e=o||ROUTE_PATHS.app.editBooking;n=selectShowWidgetButtonCopies(window.selfbookWidgetStore.getState()),o=selectRoute(window.selfbookWidgetStore.getState());const r=getElementById(SELFBOOK_WIDGET_RESUME_TEXT);let a=t.COMPLETE_BOOKING;isObjectEqual(t,n)&&o===e||(a=o===ROUTE_PATHS.app.reservations?n.VIEW_RESERVATIONS:o===ROUTE_PATHS.app.confirmation||o===ROUTE_PATHS.app.reservationDetail?n.VIEW_RESERVATION:o===ROUTE_PATHS.app.account?n.VIEW_ACCOUNT:o===ROUTE_PATHS.app.wallet?n.VIEW_WALLET:o===ROUTE_PATHS.app.bookingConfirmed?n.RETURN_BOOKING:n.COMPLETE_BOOKING,r&&(r.innerHTML=a))}catch(t){console.error("handleStoreChange: err: ",t)}}function p(){return THE_LINE_HOTELS.includes(t)?"Lydian BT":"Main"}function u(e){const n=e?.colors||{},o=`\n position: fixed;\n overflow: hidden;\n cursor: pointer;\n display: flex;\n justify-content: space-evenly;\n align-items: center;\n width: 217px;\n height: 46px;\n box-shadow: 0px 0px 2px rgba(0, 0, 0, 0.25), 0px 4px 4px rgba(0, 0, 0, 0.1);\n right: 30px;\n z-index: 100000000000;\n bottom: 40px;\n font-family: ${p()}!important;\n font-size: 14px;\n line-height: 142%;\n letter-spacing: 0.02em;\n text-transform: capitalize!important;\n border: none;\n transform: translateX(300px);\n `,r="\n background: #343A40;\n color: #FFFFFF;\n ",a=`\n ${r}\n border-radius: 23px;\n `,s=`\n background: ${n.primary_color||n.primary};\n color: ${n.secondary_color||n.secondary}!important;\n `;return i=e?.id||t,BLACK_SQUARE_THEME_HOTELS.includes(i)?`${o}${r}`:n.primary_color&&n.secondary_color||n.primary&&n.secondary?`${o}${s}`:`${o}${a}`;var i}function m(t){const e=document.createElement("style");e.textContent=t,document.head.append(e)}function y(n,o){if(!r)return;m(`#${SELFBOOK_WIDGET_RESUME} {\n ${u(o)}\n }`),document.body.style.overflow="hidden";const a=getElementById("selfbook_sdkwidget"),s=getElementById("selfbook_sdkwidget_wrapper"),l=getElementById(SELFBOOK_WIDGET_RESUME),d=l?l.getAttribute("class"):null;d&&d.includes("dismiss-btn")&&(l.removeAttribute("class"),l.setAttribute("class","dismiss-btn-slide-out")),window.selfbookWidgetStore||(s.appendChild(function(){const t=document.createElement("div");return t.id="selfbook-loading",t.style.color="white",t.style.marginRight="-50%",t.style.position="absolute",t.style.top="50%",t.style.left="50%",t.style.fontFamily="sans-serif",t.style.transform="translate(-50%, -50%)",t.innerHTML='<img src="https://sdk.selfbook.com/assets/selfbook_loading_logo.svg" height="20" width="100%">',t}()),s.style.zIndex=1e11,s.style.height="100%",s.style.background="rgba(0, 0, 0, .74)",document.body.style.overflow="hidden"),function(){if(!0===i)return;r&&r.length>0&&r.forEach((t=>{if(t.includes(".js")){const e=document.createElement("script");e.type="text/javascript",e.src=`${SELFBOOK_BASE_URL}/${t}`,document.body.appendChild(e)}}));(function(){try{const t=document.createElement("script");t.innerHTML='\n !function(){var analytics=window.analytics=window.analytics||[];if(!analytics.initialize)if(analytics.invoked)window.console&&console.error&&console.error("Segment snippet included twice.");else{analytics.invoked=!0;analytics.methods=["trackSubmit","trackClick","trackLink","trackForm","identify","reset","group","track","ready","alias","debug","once","off","on","addSourceMiddleware","addIntegrationMiddleware","setAnonymousId","addDestinationMiddleware"];analytics.factory=function(e){return function(){var t=Array.prototype.slice.call(arguments);t.unshift(e);analytics.push(t);return analytics}};for(var e=0;e<analytics.methods.length;e++){var key=analytics.methods[e];analytics[key]=analytics.factory(key)}analytics.load=function(key,e){var t=document.createElement("script");t.type="text/javascript";t.async=!0;t.src="https://cdn.segment.com/analytics.js/v1/" + key + "/analytics.min.js";var n=document.getElementsByTagName("script")[0];n.parentNode.insertBefore(t,n);analytics._loadOptions=e};analytics._writeKey="c77UfR3BzZDUXmAth2DaHEA8kkKhDIU2";;analytics.SNIPPET_VERSION="4.15.3";\n analytics.load("c77UfR3BzZDUXmAth2DaHEA8kkKhDIU2");\n }}();\n ',document.head.appendChild(t);const e=document.createElement("script");e.type="application/ld+json",e.innerHTML='{"@context": "http://schema.org/","@type": "SoftwareApplication","name": "SelfBook"}',document.head.appendChild(e);const n=document.createElement("script");n.src="https://onboard.triptease.io/bootstrap.js",n.async=!0,n.crossOrigin="anonymous",n.type="text/javascript",document.head.appendChild(n)}catch(t){console.error("inject3rdPartyScripts: err: ",t)}})(),i=!0}();const p=setInterval((()=>{if(window.selfbookWidgetStore){window.selfbookWidgetStore.subscribe(c);!function(){const t=document.getElementById("selfbook-loading");t&&getElementById("selfbook_sdkwidget_wrapper").removeChild(t)}(),clearInterval(p),window.selfbookWidgetStore.dispatch({type:BOOTSTRAP_ACTION,payload:{hotelInfo:{apiKey:n.apiKey||e,hotelId:n.hotelId||t,hotelIdAsArg:!!n.hotelId},bookingData:n,hotelGroupInfo:getFromLocalStorage(localStorage,SELFBOOK_HOTEL_GROUP_INFO)}}),"none"===a.style.display&&(s.style.zIndex=1e11,s.style.height="100%",s.style.background="rgba(0, 0, 0, .4)",document.body.style.overflow="hidden"),setTimeout((()=>{a.style.display="block",a.setAttribute("class","slide-in")}),20),setTimeout((()=>{const t=s.querySelector('[aria-modal="true"]');t&&t.focus()}),250),function(){const t=getElementById("selfbook_sdkwidget"),e=getElementById("selfbook_sdkwidget_wrapper"),n=getElementById(SELFBOOK_WIDGET_RESUME);n.addEventListener("click",(o=>{try{if(n){const o=n?n.getAttribute("class"):null;o&&o.includes("dismiss-btn")&&(n.removeAttribute("class"),n.setAttribute("class","dismiss-btn-slide-out")),document.body.style.overflow="hidden",e.style.zIndex=1e11,e.style.height="100%",e.style.background="rgba(0, 0, 0, .5)",t.style.display="block",t.setAttribute("class","slide-in"),window.selfbookWidgetStore.dispatch({type:"ANALYTICS/TRACK_EVENT",payload:{eventType:COMPLETE_BOOKING_BTN_CLICK,eventPayload:null}}),setTimeout((()=>{const t=e.querySelector('[aria-modal="true"]');t&&t.focus()}),500)}}catch(t){console.error("handleWidgetCloseListeneres: err: ",t)}}))}()}}),100)}function f(){try{const{isExpired:t,core:e,booking:n}=getPersistedData(localStorage),o=!!e?.hotel?.data?.feature_flags?.persist_state;if(t||!e?.hotel?.data?.id||!o)return void function(){try{localStorage.removeItem("persist:root")}catch(t){console.error("removePersistedData: err",t)}}();const r=e.bootstrapArgs;E({...r,startDate:n.bookingForm.start_date.slice(0,10),endDate:n.bookingForm.end_date?.slice(0,10)||null,guests:n.bookingForm.guests,propertyId:n.bookingForm.property_id,persistActive:!0,hotelId:e.hotel.data.id})}catch(t){console.error("openPersistWidget: err",t)}}const g=async()=>await fetch(`${SELFBOOK_BASE_URL}/asset-manifest.json`,{headers:{"content-type":"application/json"}}).then((t=>t.json())).then((t=>function(t){r=t.entrypoints}(t)));async function E(e={}){try{e.apiKey&&await k(e.apiKey,e.hotelId);const n=getHotelBasicData(localStorage,e.hotelId||t);if(e.guests?e.guests=fillGuestsFields(e.guests,n):e.guests=[{type:GUEST_TYPE.ADULT,count:n.default_adult_occupancy?n.default_adult_occupancy:2}],console.log(e),n.redirect_to_synxis){const t=buildRedirectSynxisLink(e,n);return void window.open(t,"_blank").focus()}y(e,n),document.getElementsByTagName("html")[0].setAttribute("translate","no")}catch(t){console.error("bookNow: err: ",t)}}function h(t){const e={startdate:"startDate",enddate:"endDate",rateplancode:"ratePlanCode",rate:"rate",roomid:"roomId",propertyid:"propertyId",destinationid:"destinationId",room:"room",adult:"adult",child:"child",infants:"infants",currency:"currency",group:"group",locale:"locale",hotel:"hotel",promo:"promo",promocode:"promocode",selfbook:"selfbook",iatanumber:"iataNumber",couponcode:"couponCode",nights:"nights",reservationid:"reservationId",lastname:"lastName",search:"search",sbsearch:"sbsearch",threeDsContinueId:"three_ds_continue_id",status:"status",roomcategory:"roomCategory",properties:"properties",source:"source"};return Array.from(t.entries()).reduce(((t,[n,o])=>({...t,[e[n.toLowerCase()]]:o})),{})}function _(){setTimeout((()=>{const e=setInterval((()=>{if(a)return;clearInterval(e);const n=h(new URLSearchParams(window.location.search));"true"===n.selfbook&&b(n,getHotelBasicData(localStorage,n.hotel||t))}),100)}),500)}function b(t,e={}){const n=parseInt(e?.default_adult_occupancy)||2,o=[{type:"adult",count:t.adult||n},{type:"child",count:t.child||0},{type:"infants",count:t.infants||0}];E({...t,guests:o,groupCode:t.group,promoCode:t.promo||t.promocode,hotelId:t.hotel||void 0,roomCategory:"string"==typeof t.roomCategory?t.roomCategory.split(",").map((t=>t.trim())):void 0})}async function w(){!function(){const t=document.createElement("div"),e=document.createElement("div"),n=document.createElement("button"),o=document.createElement("span"),r=document.createElement("img");n.id=SELFBOOK_WIDGET_RESUME,n.style.cursor="pointer",r.src="https://sdk.selfbook.com/assets/resume-icon-black.png",r.alt="Resume booking",r.width="16",r.height="16",o.innerHTML="Complete your booking",o.id=SELFBOOK_WIDGET_RESUME_TEXT,n.appendChild(o),n.appendChild(r),t.setAttribute("id","selfbook_sdkwidget"),t.style.display="none",e.setAttribute("id","selfbook_sdkwidget_wrapper"),e.style.background="rgba(0, 0, 0, 0)",e.style.position="fixed",e.style.top="0",e.style.right="0",e.style.width="100%",e.style.transition="background .5s ease-out",e.appendChild(t),document.body.appendChild(e),document.body.appendChild(n)}(),await g(),function(){if(!0!==l){if(r&&r.length>0){const t=document.getElementsByTagName("head")[0];r.forEach((e=>{if(e.includes(".css")){const n=document.createElement("link");n.rel="stylesheet",n.type="text/css",n.media="all",n.href=`${SELFBOOK_BASE_URL}/${e}`,t.appendChild(n)}}))}l=!0}}(),f(),document?.querySelectorAll("a").forEach((e=>{e.addEventListener("click",(e=>{try{"A"!==e.target.tagName&&"A"===e.target.parentNode?.tagName&&(e.target.href=e.target.parentNode.href);const n=e.target.href||"";if(-1===n.indexOf("?"))return;const o=h(new URLSearchParams(n.substring(n.indexOf("?")))),r=getHotelBasicData(localStorage,o.hotel||t);if(r?.mobile_display_only&&window.innerWidth>=768)return;(isTemplateDomainMatch(r,e)||"true"===o.selfbook)&&(e.preventDefault(),b(o,r))}catch(t){console.error("a-element: err: ",t)}}))})),"complete"===document?.readyState?_():window?.addEventListener("load",_)}async function k(t,e){a=!0;const n=e?`?id=${e}`:"",o=await d({url:`${SELFBOOK_BACKEND_URL}/hotels/info${n}`,headers:{"content-type":"application/json","API-Key":t}});localStorage.setItem(SELFBOOK_HOTEL_GROUP_INFO,JSON.stringify(o)),s||w(),s=!0,a=!1}const S=document?.getElementById("selfbook_jssdk");if(S){const n=new URL(S.getAttribute("src"));if(t=n.searchParams.get("hotelId"),e=n.searchParams.get("apiKey"),!e||!t)throw new Error("(!) selfbook: ApiKey and HotelId are required.");window.isSelfbookSDKActive=!0,k(e,t),intervalWrapper((()=>{k(e,t)}),3e5),function(){const t=`\n #selfbook_sdkwidget {\n position: absolute;\n width: 100%;\n height: 100%;\n top: 0px;\n right: 0px;\n }\n\n #${SELFBOOK_WIDGET_RESUME} {\n ${u()}\n }\n\n #${SELFBOOK_WIDGET_RESUME_TEXT} {\n display: block!important;\n font-family: ${p()}!important;\n font-size: 14px!important;\n font-weight: 400!important;\n color: #ffffff!important;\n }\n\n .dismiss-btn-slide-in {\n -webkit-transition: 500ms;\n -moz-transition: 500ms;\n transition: 500ms;\n transform: translateX(0)!important\n }\n\n .dismiss-btn-slide-out {\n -webkit-transition: 500ms;\n -moz-transition: 500ms;\n transition: 500ms;\n transform: translateX(300px)!important\n }\n\n .dismiss-btn-slide-out span {\n display: none;\n }\n\n .payment-summary-wrapper {\n overflow: scroll!important;\n -ms-overflow-style: none!important;\n scrollbar-width: none!important;\n }\n .payment-summary-wrapper::-webkit-scrollbar {\n display: none!important;\n }\n\n .dismiss-btn-resize-in {\n width: 46px!important;\n height: 46px!important;\n transition: 500ms;\n right: 30px!important;\n }\n\n .dismiss-btn-resize-in span {\n display: none;\n }\n\n .dismiss-btn-resize-in:hover {\n width: 217px!important;\n height: 46px!important;\n transition: all 300ms linear;\n cursor: pointer;\n }\n\n .dismiss-btn-resize-in:hover span {\n display: inline;\n height: 20px!important;\n overflow: hidden;\n }\n\n .dismiss-btn-resize-out {\n width: 46px!important;\n height: 46px!important;\n transition: 500ms;\n right: -100px!important;\n }\n\n .slide-in {\n transform: translateX(100%);\n -webkit-transform: translateX(100%);\n animation: slide-in 0.8s forwards !important;\n -webkit-animation: slide-in 0.8s forwards !important;\n z-index: 1000000000000;\n }\n\n .slide-out {\n transform: translateX(100%);\n -webkit-transform: translateX(100%);\n animation: slide-out 1s forwards !important;\n -webkit-animation: slide-out 1s forwards !important;\n z-index: -1000000000000;\n }\n\n @media screen and (max-width:495px) {\n .slide-in {\n transform: translateY(100%);\n -webkit-transform: translateY(100%);\n animation: slide-up 0.8s forwards !important;\n -webkit-animation: slide-up 0.8s forwards !important;\n z-index: 1000000000000;\n }\n .slide-out {\n transform: translateY(100%);\n -webkit-transform: translateY(100%);\n animation: slide-down 0.5s forwards !important;\n -webkit-animation: slide-down 1s forwards !important;\n z-index: -1000000000000;\n }\n }\n\n @keyframes slide-in {\n 100% {\n transform: translateX(0%);\n }\n }\n\n @-webkit-keyframes slide-in {\n 100% {\n -webkit-transform: translateX(0%);\n }\n }\n\n @keyframes slide-up {\n 100% {\n transform: translateY(0%);\n }\n }\n\n @-webkit-keyframes slide-up {\n 100% {\n -webkit-transform: translateY(0%);\n }\n }\n\n @keyframes slide-out {\n 0% {\n transform: translateX(0%);\n }\n 100% {\n transform: translateX(100%);\n }\n }\n\n @-webkit-keyframes slide-out {\n 0% {\n -webkit-transform: translateX(0%);\n }\n 100% {\n -webkit-transform: translateX(100%);\n }\n }\n\n @keyframes slide-down {\n 0% {\n transform: translateY(0%);\n }\n 100% {\n transform: translateY(100%);\n }\n }\n\n @-webkit-keyframes slide-down {\n 0% {\n -webkit-transform: translateY(0%);\n }\n 100% {\n -webkit-transform: translateY(100%);\n }\n }\n `,e=document.createElement("style");e.textContent=t,document.head.append(e),m(t)}()}window&&(window.sbApiLogger=function(n){try{d({url:`${SELFBOOK_BACKEND_URL}/hotels/${t}/events`,method:"POST",headers:{"content-type":"application/json","API-Key":e},data:JSON.stringify({generated_at:(new Date).toISOString(),event_source:"SDK",body:n})})}catch(t){console.log(t)}},window.book=function(t,e,n,o,r,a,s,i,l,d,c="fr",p,u,m){E({startDate:t,endDate:e,guests:n,propertyId:o,currency:r,roomId:a,ratePlanCode:s,rate:i,promoCode:l,groupCode:d,locale:c,destinationId:u,hotelId:p,roomCategory:m})},window.bookNow=E,window.closeSelfbookWidget=function(){const t=getElementById("selfbook_sdkwidget"),e=getElementById("selfbook_sdkwidget_wrapper"),n=getElementById(SELFBOOK_WIDGET_RESUME);document.body.style.overflow="initial",t.setAttribute("class","slide-out"),e.style.background="rgba(0, 0, 0, 0)",window.selfbookWidgetStore.dispatch({type:"WIDGET/SELFBOOK_WIDGET_CLOSED"}),window.selfbookWidgetStore.dispatch({type:"ANALYTICS/TRACK_EVENT",payload:{eventType:"widget closed"}}),setTimeout((()=>{t.style.display="none",e.style.zIndex=-1e11;const o=getFromLocalStorage(localStorage,SELFBOOK_HOTEL_GROUP_INFO);o&&(o.maintenance_mode||(n.setAttribute("class","dismiss-btn-slide-in"),n.focus()))}),800)})}runDirectApplication(); /* ---------------------------------- Custom integration script for The Walker Hotels. https://www.walkerhotels.com/ Chain code: 19191 ---------------------------------- */ function waitForElm(selector) { /* This is used to watch for elements that are dynamically added to the DOM. 1. We create a Promise based MutationObserver. 2. We observe the body of the document. 3. We check if the document.querySelector(selector) returns a value. 4. If it does, we return the value of the Promise. 5. If it doesn't, we return the MutationObserver. 6. We disconnect the MutationObserver. */ return new Promise(function (resolve) { if (document.querySelector(selector)) { return resolve(document.querySelector(selector)); } const observer = new MutationObserver(function (mutations) { if (document.querySelector(selector)) { resolve(document.querySelector(selector)); observer.disconnect(); } }); observer.observe(document.body, { childList: true, subtree: true, }); }); } function replaceNavDesktopCTA(selector) { /* Clone Top-right CTA */ // Get the element var elem = document.querySelector(selector); // Create a copy of it var clone = elem.cloneNode(true); // Update the ID and add a class clone.setAttribute("onclick", "bookNowByHotel()"); // Inject it into the DOM elem.after(clone); elem.remove(); } function bookNowByHotel() { /* 1. We check if the URL contains the word "greenwich" or "tribeca". 2. If it does, we use the hotelId of the hotel that we want to book. 3. If it doesn't, we use the hotelId of the hotel that we are currently in. 4. We use the bookNow function to book the hotel. 5. We use the checkCookieBanner function to check if the cookie banner is displayed. */ if (location.href.includes("greenwich")) { bookNow({ hotelId: "69634" }); } else if (location.href.includes("tribeca")) { bookNow({ hotelId: "8883" }); } else { bookNow(); } checkCookieBanner(); } waitForElm("#main > header > div.reserve-btn").then(function (elm) { /* This is a function that waits for the Desktop CTA to be loaded. It calls the function replaceNavDesktopCTA("#main > header > div.reserve-btn") which waits for, then clones the Desktop CTA.*/ replaceNavDesktopCTA("#main > header > div.reserve-btn"); console.log("Desktop CTA is changed"); }); waitForElm("#main > header > div.reservation-bg").then(function (elm) { /* This is a function that waits for the Desktop Calendar to be loaded. Then, the function removes the element */ elm.remove(); console.log("Desktop Calendar detected"); }); function checkCookieBanner() { /* If cookie is still open when a user clicks on bookNow button, the cookie banner element winds up getting expanded. To prevent that issue, we: 1. Check if the cookie banner exists. 2. If the cookie banner exists, check if it has any children. 3. If the cookie banner has children, remove it. */ if (document.querySelector("#msCookieBanner")) { if (document.querySelector("#msCookieBanner").childElementCount) { console.log("Cookie detected"); document.querySelector("#msCookieBanner").remove(); } } } let replacedLinks = 0; /* Checks the format of the inputted date and puts it in the correct order format (YYYY-MM-DD) */ function convertDate(date) { const inputDate = new Date(date); const year = inputDate.getFullYear(); const month = (inputDate.getMonth() + 1).toString().padStart(2, '0'); const day = inputDate.getDate().toString().padStart(2, '0'); return [year, month, day].join('-'); }; /* Validates date format is 'YYYY-MM-DD' */ function checkDateIsISOformat(inputString) { const regexPattern = /^\d{4}-\d{2}-\d{2}$/; return regexPattern.test(inputString); } /* Verifies these date formats 'DD MONTH YYYY' 'YYYY-MM-DD' 'YYYY/MM/DD' 'MM/DD/YYYY' */ function verifyFutureDate(date) { if (date) { if (checkDateIsISOformat(date)) { const inputDate = new Date(date); const today = new Date(); today.setHours(0, 0, 0, 0); if (inputDate > today) { const formattedDate = inputDate.toISOString().split('T')[0]; return formattedDate; } } else { const inputDate = new Date(date); const today = new Date(); today.setHours(0, 0, 0, 0); if (inputDate > today) { return convertDate(inputDate); } } } return ''; } /* This function filters the initial parameters and returns the ones that are null of undefined, it also flattens the array / object within guests */ const pruneObjectKeys = (object) => { const pruneObject = object; Object.keys(pruneObject).forEach((key) => { if (key === 'guests') { if (pruneObject[key][0] !== null && pruneObject[key][0].count > 0) { const adultCount = pruneObject[key][0].count; pruneObject.adult = adultCount; if ( pruneObject[key][1] !== null && pruneObject[key][1].count > 0) { const childCount = pruneObject[key][1].count; pruneObject.child = childCount; } } if ( pruneObject[key] !== null && pruneObject[key][1].count !== null) { const childCount = pruneObject[key][1].count; pruneObject.child = childCount; } else { delete pruneObject[key]; } } if (!pruneObject[key]) { // console.log(pruneObject[key]); delete pruneObject[key]; } return pruneObject; }); return pruneObject; }; /* Fix for Currency Case Bug */ const upperCaseCurrency = (currency) => { if (!currency) { return null; } const currencyCode = currency.toUpperCase(); return currencyCode; }; /* This function defines the bookNow() Object and assigns the values after running it through pruneObjectKeys(). */ const assignObjectVals = (param) => { const urlData = param; // console.log(param); const bookNowParams = { startDate: verifyFutureDate(urlData.arrive), endDate: verifyFutureDate(urlData.depart), guests: [ { type: 'adult', count: (!urlData.adult ? null : parseInt(urlData.adult, 10)), }, { type: 'child', count: (!urlData.child ? null : parseInt(urlData.child, 10)), }, ], propertyId: urlData.propertyId, currency: upperCaseCurrency(urlData.currency), roomId: urlData.room, ratePlanCode: urlData.rate, promoCode: urlData.promo, groupCode: urlData.group, locale: urlData.locale, hotelId: urlData.hotel, }; return pruneObjectKeys(bookNowParams); }; // Create a function that parses the URL and returns an object with the params const parseURL = (url) => { const params = {}; let urlInput = url.replace(/%20/g, ''); urlInput = urlInput.toLowerCase().split('?'); const urlParams = urlInput[1].split('&'); for (let i = 0; i < urlParams.length; i++) { const param = urlParams[i].split('='); params[param[0]] = param[1]; } const searchParams = decodeURIComponent( new URLSearchParams(assignObjectVals(params)), ).toString(); // eslint-disable-next-line max-len const domain = `https://${window.location.hostname}?selfbook=true&${searchParams}`; return [domain, params, searchParams]; }; /* this finds all <a> tags and checks that thier hrefs contain a parameter then runs a loop that replaces that href and adds an event listener for the bookNow() */ const linkReplacer = () => { const allSynxisLinks = document.querySelectorAll(`a[href*='synxis']`); // const allSynxisLinks = document.querySelectorAll(`a[href*='reservations'], a[href*='synxis']`); for (let i = 0; i < allSynxisLinks.length; i++) { allSynxisLinks[i].removeAttribute('target'); const atag = allSynxisLinks[i]; const link = allSynxisLinks[i].href; const parsedLink = parseURL(link); const finalParams = assignObjectVals(parsedLink[1]); atag.href = "#" + parseURL(link)[2]; // console.log('parseURL(link)[0]', parseURL(link)[0]); // console.log('finalParams', finalParams); // atag.href = '#'; atag.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); e.stopImmediatePropagation(); checkCookieBanner(); bookNow(finalParams); }); replacedLinks += 1; } }; linkReplacer(); // Walker hotels (both Greenwich & Tribeca) would like to change the copy on this Most Popular CTA from CONTINUE to ACCEPT & CONTINUE function newMpCopy() { if (document.querySelector("#new-sb-flex-cancel") && document.querySelector("#new-sb-flex-cancel").parentElement?.parentElement?.querySelectorAll('button').length > 2) { document.querySelector("#new-sb-flex-cancel").parentElement.parentElement.querySelectorAll('button').forEach(button => { if (button.textContent.toLowerCase() === "continue") { let text = "ACCEPT & CONTINUE"; button.innerText = text; } }); } } setInterval(() => { linkReplacer(); newMpCopy(); }, 1000); document.addEventListener("click", function (e) { if (e.target && e.target.nodeName == "A") { let hlink = e.target.href.toString().toLowerCase(); if (hlink.includes("synxis")) { e.preventDefault(); e.stopPropagation(); e.target.removeAttribute("target"); checkCookieBanner(); let parsedLink = parseURL(hlink); bookNow(assignObjectVals(parsedLink[1])) } } }); // Bugsnag API // https://bugsnagerrorreportingapi.docs.apiary.io/# const API_KEY_BUGSNAG = '6cb771be223c608f92775d1516dce6e2'; const CLIENT_URL = window.location.href; async function sendErrorToBugsnag(error, func, sb_int_queue) { const apiKey = API_KEY_BUGSNAG; const apiUrl = 'https://notify.bugsnag.com'; const payload = { apiKey: apiKey, notifier: { name: 'Custom Notifier', version: '1.0', url: CLIENT_URL, }, events: [{ payloadVersion: '5', exceptions: [{ errorClass: 'SbIntegration', message: `${error.name ? error.name : ""}. Message: ${error.message}.`, stacktrace: [{ file: CLIENT_URL, method: " (" + func + " Error) ", code: { 1: `${error.name ? error.name : ""}. Message: ${error.message}.`, 2: "Browser: " + navigator.userAgent, 3: "Language: " + navigator.language }, }], }], severity: 'error', context: CLIENT_URL, app: { releaseStage: sb_int_queue ? sb_int_queue : "" } }] }; const headers = new Headers({ 'Content-Type': 'application/json', 'Bugsnag-Api-Key': apiKey, 'Bugsnag-Payload-Version': '5', 'Bugsnag-Sent-At': new Date().toISOString() }); const requestOptions = { method: 'POST', headers: headers, body: JSON.stringify(payload) }; try { const response = await fetch(apiUrl, requestOptions); if (response.ok) { console.log('Error report sent successfully'); } else { console.error('Failed to send error report'); } } catch (error) { console.error('Error sending error report:', error); } }; function handleError(error, func, sb_int) { console.error(`${func} error occurred:`, error); sendErrorToBugsnag(error, func, sb_int); // Forward the error to Bugsnag reporting }; // Global error capture window.onerror = (message, source, lineno, colno, error) => { // Returning true prevents the default browser error handling if (message && !message?.toString().toLowerCase().includes('resizeobserver') && !message?.toString().toLowerCase().includes('intersectionobserver') && !source?.toString().toLowerCase().includes('guestbook') && !source?.toString().toLowerCase().includes('walkerhotels')) { const customError = { name: message?.toString().split(' ')[0], message: ` message: ${message}. source: ${source}. lineno: ${lineno}. colno: ${colno}. error: ${error}.`, }; if ( source && ( source.toString().includes('react') || source.toString().includes('saga.js') || source.toString().includes('affirm.js') ) ) { handleError(customError, 'selfbook-crash', 'sb_integrations'); return true; } else if ( source && source.toString().includes('selfbook') && !source.toString().includes('selfbook=true') ) { handleError(customError, 'window.onerror'); return true; } } };