Skip to main content

Embed & Initialize

To correctly load, the SDK needs to be initialized with a configuration that will be specific to each partner hotel.

  1. See Theming via the Theming API documentation for more details.
  2. Initialization setup via the Configuration API to allow the widget to be launched via HTML elements on your web page. See the Configuration API documentation for more details.

Including the SDK

To include the SDK on a page in a customer's website, add the following tags to the head of the web page:

  • <script> tag containing inline JavaScript. This is responsible for initializing the Skipper booking engine.
  • <link/> tags - these are enhance load time performance of the Skipper Booking Engine by preloading the comprising assets

Copy the following code within the comments and paste within the of each web page to implement. Note: these will need to be included on every web page on which a partner wants to see Skipper-driven behavior.

<html>
<head>
<!-- ... Copy tags below -->
<script>
"use strict";var Y=Object.defineProperty;var z=(e,n,i)=>n in e?Y(e,n,{enumerable:!0,configurable:!0,writable:!0,value:i}):e[n]=i;var d=(e,n,i)=>z(e,typeof n!="symbol"?n+"":n,i);class H{constructor(n=!0){d(this,"_eventMap");d(this,"_recalculateExport");d(this,"_exports");d(this,"_addTimestamps");this._eventMap=new Map,this._recalculateExport=!0,this._exports=[],this._addTimestamps=n}track(n,i){this._addTimestamps&&(i.eventTimestamp=new Date().toISOString()),this._eventMap.set(n,i),this._recalculateExport=!0}getEvent(n){return this._eventMap.get(n)}export(){if(this._recalculateExport){this._exports=[];const n=this._eventMap[Symbol.iterator]();for(const[i,a]of n)this._exports.push({eventName:i,payload:a})}return this._recalculateExport=!1,this._exports}clear(){this._recalculateExport=!0,this._exports=[],this._eventMap.clear()}}var m=(e=>(e.OVERLAY="overlay",e.SIDERAIL="sideRail",e))(m||{});const W={WIDGET_LOAD_TIME:"Widget Load Time",INITIAL_INTERACTION_TIME:"Initial Interaction Time",SCRIPT_INSERTION_TIME:"Script Insertion Time"},q=3e4,O=700,G=1200,Q=1e3,y=new H,F=Date.now();let N,_,R,E,k=!0,$=!1,u=!1,l=null,v=null,x=!1,p=null;window.initSkipper=et;function Z(e,n,i){const{primaryColor:a,primaryFont:h,secondaryFont:o}=n,w=i===m.OVERLAY?1e3:500,T=i===m.OVERLAY?"margin: auto !important; left: 0 !important; bottom: 0 !important; max-height: 740px !important;":"",I='<div id="skipper-interstitial-container"><svg id="interstitial-animation" width="48" height="16" viewBox="0 0 32 16" fill="none" xmlns="http://www.w3.org/2000/svg"><g id="cw-circle-container"><circle id="circle-fill" cx="24" cy="8" r="8"/><circle id="circle-stroke" cx="8" cy="8" r="7" stroke-width="2"/></g></svg><p id="message">Loading...</p></div>',g=`@media only screen and (min-width: 768px) {#skipper-interstitial-container {width: ${w}px !important; ${T}}} #circle-stroke{animation: ltor 1.2s ease-in-out infinite;animation-direction: alternate;stroke: ${a||"#2563EB"}}#circle-fill{animation: rtol 1.2s ease-in-out infinite;animation-direction: alternate;fill: ${a||"#2563EB"}}@keyframes ltor{20%{transform: translateX(-4px)}50%{transform: translateX(20px)}80%{transform: translateX(-4px)}}@keyframes rtol{20%{transform: translateX(4px)}50%{transform: translateX(-20px)}80%{transform: translateX(4px)}}.cash-widget-overlay-container:before{content: "";width: 100%;height: 100%;background-color: #00000080;position: fixed;bottom: 0;left: 0;right: 0;top: 0;z-index: 99}`;document.getElementById(e).insertAdjacentHTML("afterend",I),l=document.getElementById("skipper-interstitial-container"),l.style=`position: fixed; height: 100vh; background: #FFFFFF; z-index: 2147483647; overflow: auto; display: initial; width: 100vw; width: -webkit-fill-available; width: -moz-available; width: stretch; top: 0px; left: initial; right: 0px; display: flex; flex-direction: column; align-items: center; justify-content: center; font-family: ${h}, ${o}, 'Verdana'; text-align: center;`;const c=document.createElement("style");c.textContent=g,document.head.append(c),document.body.classList.add("cash-widget-overlay-container")}function J(){const e=new URL(window.location.href);return e.searchParams.get("sbe_openBE")==="true"||e.searchParams.get("openBE")==="true"}function K({hotelId:e,groupId:n,brandId:i}){const a=()=>{const o=[];return n&&o.push(`group-${n}`),i&&o.push(`brand-${i}`),e&&o.push(`hotel-${e}`),o.join("--")};return localStorage.getItem(`${a}_display`)}const tt=(e,n={},i)=>()=>{u||(_=Date.now(),y.track(W.INITIAL_INTERACTION_TIME,{interactionTime:_-F}),Z(e,n,i)),u=!0,x=!0};async function P(){var e;u&&window.openWidget&&$&&k&&(await window.openWidget(),(e=l==null?void 0:l.remove)==null||e.call(l))}function et(e){const{target:n,style:i,fallbackUrl:a,isDynamic:h=!1,dynamicQueryInterval:o=Q,renderAs:w,selfHosted:T}=e,{targetClasses:I=[],targetIds:g=[]}=e,c=new URL(window.location.href).searchParams.get("sbe_renderAs"),L=c||null||(T?m.OVERLAY:w||m.OVERLAY);e.renderAs=L;const A=tt(n,i,L);(J()||K({...e,type:"display"})==="collapsed")&&(setTimeout(()=>{const t=new CustomEvent("openBE");document.dispatchEvent(t)}),setTimeout(()=>{E?u=!0:A()},O));const U=()=>{R=Date.now();const t=document.createElement("script"),s="production";{let r;switch(s){case"production":r="widget.skipperhospitality.com";break;case"stage":r="widget-stage.skipperhospitality.com";break;case"uat":r="widget-uat.skipperhospitality.com";break;default:r="widget-dev.skipperhospitality.com";break}t.src=`https://${r}/cash-sdk.umd.js`}t.type="module",t.addEventListener("load",()=>{E=Date.now();const r=new CustomEvent("skipper-hydrated",{bubbles:!0});t.dispatchEvent(r)}),setTimeout(()=>{E||(k=!1,setTimeout(async()=>{k=!0,await P()},G))},O),setTimeout(()=>{u&&x&&a&&window.open(a,"_blank")},q),document.head.appendChild(t)};document.addEventListener("DOMContentLoaded",()=>{N=Date.now()});const b=new AbortController,V=t=>{document.addEventListener(t,()=>{b.abort(),U()},{once:!0,signal:b.signal})};["mousedown","scroll","keydown","touchstart","mousemove","openBE"].forEach(t=>V(t)),document.addEventListener("skipper-hydrated",async()=>{window._initSkipper&&await window._initSkipper(e,v)},{once:!0}),document.addEventListener("skipper-initialized",async()=>{p&&clearInterval(p),f.forEach(t=>{t.removeEventListener("click",S)}),x=!1,$=!0,window.primeTracking(y.export(),{pageLoadTime:F,initialUserInteractionTime:_,skipperScriptInsertionTime:R,skipperScriptLoadedTime:E,DOMContentLoadedTime:N}),await P(),y.clear()},{once:!0});function S(t){v=t.currentTarget?t.currentTarget:v,p&&clearInterval(p),t.preventDefault(),A()}const C=(t,s)=>{let r=[];return typeof t=="string"&&(t=[t]),t.length&&(r=Array.from(document.querySelectorAll(t.map(j=>`${s==="class"?".":"#"}${j}`).join(",")))),r},X=["[data-skipper-internal-link]","[href*=sbe_internalLink]"],M=()=>{var t;return new Set([...C(I,"class"),...C(g,"id"),...((t=document.querySelectorAll(X.join(",")))==null?void 0:t.values())||[]])},D=t=>t.addEventListener("click",S),f=M();for(const t of f)D(t);h&&typeof o=="number"&&(p=setInterval(()=>{const t=M();for(const s of f)t.delete(s);for(const s of t)f.add(s),D(s)},o))}
</script>
<link
rel="preload"
href="https://widget.skipperhospitality.com/cash-sdk.umd.js"
as="script"
crossorigin
/>
<link
rel="preload"
href="https://widget.skipperhospitality.com/style.css"
as="style"
crossorigin
/>
<link
rel="preload"
href="https://widget.skipperhospitality.com/site-base.css"
as="style"
crossorigin
/>
<link rel="preconnect" href="https://cash-api.skipperhospitality.com" />
<!-- ... End copy -->
</head>
</html>

Options and Notes

  • The <link rel="preload" ... /> tags are not strictly necessary, but will help to make the booking engine is loaded and available as soon as possible
  • The <link rel="preconnect" ...> tag is not strictly necessary, but can provide a slight performance benefit in connecting with the Skipper API
  • Instead of using the inline <script>...</script>, you can use <script src="https://widget.skipperhospitality.com/cash-sdk-load.umd.js"></script>. This will, however incur a performance cost.
  • Use this UMD load script version of our inline script if you encounter WordPress plugin compatibility issues:
     <script>
    (function(a){typeof define=="function"&&define.amd?define(a):a()})(function(){"use strict";var ee=Object.defineProperty;var te=(a,r,p)=>r in a?ee(a,r,{enumerable:!0,configurable:!0,writable:!0,value:p}):a[r]=p;var h=(a,r,p)=>te(a,typeof r!="symbol"?r+"":r,p);class a{constructor(n=!0){h(this,"_eventMap");h(this,"_recalculateExport");h(this,"_exports");h(this,"_addTimestamps");this._eventMap=new Map,this._recalculateExport=!0,this._exports=[],this._addTimestamps=n}track(n,i){this._addTimestamps&&(i.eventTimestamp=new Date().toISOString()),this._eventMap.set(n,i),this._recalculateExport=!0}getEvent(n){return this._eventMap.get(n)}export(){if(this._recalculateExport){this._exports=[];const n=this._eventMap[Symbol.iterator]();for(const[i,s]of n)this._exports.push({eventName:i,payload:s})}return this._recalculateExport=!1,this._exports}clear(){this._recalculateExport=!0,this._exports=[],this._eventMap.clear()}}var r=(t=>(t.OVERLAY="overlay",t.SIDERAIL="sideRail",t))(r||{});const p={WIDGET_LOAD_TIME:"Widget Load Time",INITIAL_INTERACTION_TIME:"Initial Interaction Time",SCRIPT_INSERTION_TIME:"Script Insertion Time"},V=3e4,b=700,X=1200,j=1e3,I=new a,S=Date.now();let C,g,M,E,v=!0,D=!1,m=!1,d=null,y=null,_=!1,u=null;window.initSkipper=q;function Y(t,n,i){const{primaryColor:s,primaryFont:w,secondaryFont:l}=n,k=i===r.OVERLAY?1e3:500,x=i===r.OVERLAY?"margin: auto !important; left: 0 !important; bottom: 0 !important; max-height: 740px !important;":"",L='<div id="skipper-interstitial-container"><svg id="interstitial-animation" width="48" height="16" viewBox="0 0 32 16" fill="none" xmlns="http://www.w3.org/2000/svg"><g id="cw-circle-container"><circle id="circle-fill" cx="24" cy="8" r="8"/><circle id="circle-stroke" cx="8" cy="8" r="7" stroke-width="2"/></g></svg><p id="message">Loading...</p></div>',A=`@media only screen and (min-width: 768px) {#skipper-interstitial-container {width: ${k}px !important; ${x}}} #circle-stroke{animation: ltor 1.2s ease-in-out infinite;animation-direction: alternate;stroke: ${s||"#2563EB"}}#circle-fill{animation: rtol 1.2s ease-in-out infinite;animation-direction: alternate;fill: ${s||"#2563EB"}}@keyframes ltor{20%{transform: translateX(-4px)}50%{transform: translateX(20px)}80%{transform: translateX(-4px)}}@keyframes rtol{20%{transform: translateX(4px)}50%{transform: translateX(-20px)}80%{transform: translateX(4px)}}.cash-widget-overlay-container:before{content: "";width: 100%;height: 100%;background-color: #00000080;position: fixed;bottom: 0;left: 0;right: 0;top: 0;z-index: 99}`;document.getElementById(t).insertAdjacentHTML("afterend",L),d=document.getElementById("skipper-interstitial-container"),d.style=`position: fixed; height: 100vh; background: #FFFFFF; z-index: 2147483647; overflow: auto; display: initial; width: 100vw; width: -webkit-fill-available; width: -moz-available; width: stretch; top: 0px; left: initial; right: 0px; display: flex; flex-direction: column; align-items: center; justify-content: center; font-family: ${w}, ${l}, 'Verdana'; text-align: center;`;const f=document.createElement("style");f.textContent=A,document.head.append(f),document.body.classList.add("cash-widget-overlay-container")}function z(){const t=new URL(window.location.href);return t.searchParams.get("sbe_openBE")==="true"||t.searchParams.get("openBE")==="true"}function H({hotelId:t,groupId:n,brandId:i}){const s=()=>{const l=[];return n&&l.push(`group-${n}`),i&&l.push(`brand-${i}`),t&&l.push(`hotel-${t}`),l.join("--")};return localStorage.getItem(`${s}_display`)}const W=(t,n={},i)=>()=>{m||(g=Date.now(),I.track(p.INITIAL_INTERACTION_TIME,{interactionTime:g-S}),Y(t,n,i)),m=!0,_=!0};async function O(){var t;m&&window.openWidget&&D&&v&&(await window.openWidget(),(t=d==null?void 0:d.remove)==null||t.call(d))}function q(t){const{target:n,style:i,fallbackUrl:s,isDynamic:w=!1,dynamicQueryInterval:l=j,renderAs:k,selfHosted:x}=t,{targetClasses:L=[],targetIds:A=[]}=t,f=new URL(window.location.href).searchParams.get("sbe_renderAs"),N=f||null||(x?r.OVERLAY:r.OVERLAY||k);t.renderAs=N;const R=W(n,i,N);(z()||H({...t,type:"display"})==="collapsed")&&(setTimeout(()=>{const e=new CustomEvent("openBE");document.dispatchEvent(e)}),setTimeout(()=>{E?m=!0:R()},b));const Q=()=>{M=Date.now();const e=document.createElement("script"),c="production";{let o;switch(c){case"production":o="widget.skipperhospitality.com";break;case"stage":o="widget-stage.skipperhospitality.com";break;case"uat":o="widget-uat.skipperhospitality.com";break;default:o="widget-dev.skipperhospitality.com";break}e.src=`https://${o}/cash-sdk.umd.js`}e.type="module",e.addEventListener("load",()=>{E=Date.now();const o=new CustomEvent("skipper-hydrated",{bubbles:!0});e.dispatchEvent(o)}),setTimeout(()=>{E||(v=!1,setTimeout(async()=>{v=!0,await O()},X))},b),setTimeout(()=>{m&&_&&s&&window.open(s,"_blank")},V),document.head.appendChild(e)};document.addEventListener("DOMContentLoaded",()=>{C=Date.now()});const P=new AbortController,Z=e=>{document.addEventListener(e,()=>{P.abort(),Q()},{once:!0,signal:P.signal})};["mousedown","scroll","keydown","touchstart","mousemove","openBE"].forEach(e=>Z(e)),document.addEventListener("skipper-hydrated",async()=>{window._initSkipper&&await window._initSkipper(t,y)},{once:!0}),document.addEventListener("skipper-initialized",async()=>{u&&clearInterval(u),T.forEach(e=>{e.removeEventListener("click",F)}),_=!1,D=!0,window.primeTracking(I.export(),{pageLoadTime:S,initialUserInteractionTime:g,skipperScriptInsertionTime:M,skipperScriptLoadedTime:E,DOMContentLoadedTime:C}),await O(),I.clear()},{once:!0});function F(e){y=e.currentTarget?e.currentTarget:y,u&&clearInterval(u),e.preventDefault(),R()}const $=(e,c)=>{let o=[];return typeof e=="string"&&(e=[e]),e.length&&(o=Array.from(document.querySelectorAll(e.map(K=>`${c==="class"?".":"#"}${K}`).join(",")))),o},J=["[data-skipper-internal-link]","[href*=sbe_internalLink]"],B=()=>{var e;return new Set([...$(L,"class"),...$(A,"id"),...((e=document.querySelectorAll(J.join(",")))==null?void 0:e.values())||[]])},U=e=>e.addEventListener("click",F),T=B();for(const e of T)U(e);w&&typeof l=="number"&&(u=setInterval(()=>{const e=B();for(const c of T)e.delete(c);for(const c of e)T.add(c),U(c)},l))}});
    </script>

Initializing the Booking Engine

Once the customization and configuration has been decided and the SDK has been included on the proper web pages, insert the following code snippet at the end of their web page(s) body tag.

The contents of the configuration object {} passed to the initSkipper function should match options in the Configuration API as decided in the previous Customize & Configure step.

<body>
<!-- ... -->
<!-- These tags must be direct children of the <body> tag -->
<div id="skipper-target"></div>
<script type="module" id="skipper-init-script">
// initializing Skipper widget with configuration
if (window.initSkipper) {
window.initSkipper({
target: "skipper-target",
targetIds: ["main-reserve-button"],
targetClasses: ["other-reserve-buttons"],
hotelId: "HOTELID_FROMSKIPPER", // not needed if groupId or brandId is provided
brandId: "BRANDID_FROMSKIPPER", // not needed if hotelId or groupId is provided
groupId: "GROUPID_FROMSKIPPER", // not needed if hotelId or brandId is provided
style: {
primaryColor: "#1B3A5C",
accentColor: "#7F9580",
primaryFont: "Comic Sans MS",
},
fallbackUrl: "https://<enter-fallback-CRS-or-PMS-site-here>",
});
}
</script>
</body>

Options and Notes

  • <div id="skipper-target"></div> can be inserted anywhere as a child of the body tag. The value of its id attribute will be the value of target in the initSkipper call.

  • Adding skipper-init-script as the id value for the script tag allows Revinate integrations to look for Skipper scripts.

  • Both the inline <script> and the <div id="skipper-target"></div> must be direct children of the body tag. They should not be children of other DOM elements that themselves are descendants of the body tag.

  • Either put the <script> before the closing </body> tag or use the type=module attribute on the <script> tag to ensure that the script is executed after the DOM has been fully loaded. Otherwise the widget might no work as expected.

  • If you prefer, you can omit the target parameter and instead use the custom element <skipper-booking-engine></skipper-booking-engine>:

  • Adding type="module" inside the opening script tag and using the UMD load script resolves compatibility issues with newer versions of SquareSpace websites.

<body>
<!-- ... -->
<skipper-booking-engine></skipper-booking-engine>
<script type="module" id="skipper-init-script">
// initializing Skipper widget with configuration
if (window.initSkipper) {
window.initSkipper({
targetIds: ["main-reserve-button"],
targetClasses: ["other-reserve-buttons"],
hotelId: "HOTELID_FROMSKIPPER",
style: {
primaryColor: "#1B3A5C",
accentColor: "#7F9580",
primaryFont: "Comic Sans MS",
},
fallbackUrl: "https://<enter-fallback-CRS-or-PMS-site-here>",
});
}
</script>
</body>