diff --git a/plugin/plan-your-day/assets/js/plan.js b/plugin/plan-your-day/assets/js/plan.js
index fb4b8ce..be56978 100644
--- a/plugin/plan-your-day/assets/js/plan.js
+++ b/plugin/plan-your-day/assets/js/plan.js
@@ -1008,14 +1008,19 @@
browse: config.initialData?.browse || {},
route: config.initialData?.route || {},
};
+ const canBootstrapEndpointToken =
+ typeof config.rest?.bootstrapUrl === 'string' &&
+ config.rest.bootstrapUrl !== '';
+ let endpointToken = typeof config.rest?.endpointToken === 'string' ? config.rest.endpointToken : '';
+ let hasBootstrappedEndpointToken = false;
+ let endpointTokenRequest = null;
const hasRestConfig =
refs.form instanceof HTMLFormElement &&
typeof config.rest?.browseUrl === 'string' &&
config.rest.browseUrl !== '' &&
typeof config.rest?.routeUrl === 'string' &&
config.rest.routeUrl !== '' &&
- typeof config.rest?.endpointToken === 'string' &&
- config.rest.endpointToken !== '';
+ (endpointToken !== '' || canBootstrapEndpointToken);
const shouldHydrateOnLoad = Boolean(config.hydration?.shouldHydrateOnLoad);
const colorModeDefault = normalizeColorModeDefault(
config.colorModeDefault || root.getAttribute('data-plan-color-mode-default')
@@ -1190,6 +1195,58 @@
animateStartPanel(false);
};
+ const ensureEndpointToken = async () => {
+ if (!canBootstrapEndpointToken) {
+ return endpointToken;
+ }
+
+ if (hasBootstrappedEndpointToken && endpointToken !== '') {
+ return endpointToken;
+ }
+
+ if (endpointTokenRequest) {
+ return endpointTokenRequest;
+ }
+
+ endpointTokenRequest = fetch(config.rest.bootstrapUrl, {
+ method: 'POST',
+ credentials: 'same-origin',
+ headers: {
+ Accept: 'application/json',
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({}),
+ })
+ .then(async (response) => {
+ const responseBody = await response.json().catch(() => ({}));
+ debugLog(config, response.ok ? 'info' : 'warn', 'request:bootstrap', {
+ status: response.status,
+ ok: response.ok,
+ body: responseBody,
+ });
+
+ if (!response.ok) {
+ throw new Error(responseBody?.message || strings.requestFailed || '');
+ }
+
+ const freshToken = String(responseBody?.endpointToken || '');
+
+ if (freshToken === '') {
+ throw new Error(strings.requestFailed || '');
+ }
+
+ endpointToken = freshToken;
+ hasBootstrappedEndpointToken = true;
+
+ return endpointToken;
+ })
+ .finally(() => {
+ endpointTokenRequest = null;
+ });
+
+ return endpointTokenRequest;
+ };
+
const sendRequest = async (endpointKey, payload, requestOptions = {}) => {
if (!hasRestConfig) {
return 'unsupported';
@@ -1259,9 +1316,15 @@
});
try {
+ const requestEndpointToken = await ensureEndpointToken();
+
+ if (requestEndpointToken === '') {
+ throw new Error(strings.requestFailed || '');
+ }
+
const requestBody = {
...payload,
- endpoint_token: config.rest.endpointToken,
+ endpoint_token: requestEndpointToken,
};
if (endpointKey === 'browse') {
diff --git a/plugin/plan-your-day/assets/js/plan.min.js b/plugin/plan-your-day/assets/js/plan.min.js
index b622135..46565b4 100644
--- a/plugin/plan-your-day/assets/js/plan.min.js
+++ b/plugin/plan-your-day/assets/js/plan.min.js
@@ -1 +1 @@
-(()=>{const t="planYourDayEnhanced",e="planYourDayColorMode",a="light",n="dark",r="system",o=[a,n],s=[...o,r],i="checking",l="found",u="not_found",d=[i,l,u],c=t=>String(t??"").replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'"),p=(t,e={})=>{let a=String(t||"");return Object.entries(e||{}).forEach((([t,e])=>{a=a.split(`{${t}}`).join(String(e??""))})),a},y=(t,e="")=>Array.isArray(t)?t.map((t=>y(t))):t&&"object"==typeof t?Object.fromEntries(Object.entries(t).map((([t,e])=>{const a=String(t).toLowerCase();return[t,a.includes("token")||a.includes("api_key")||a.includes("authorization")||a.includes("cookie")||a.includes("secret")?"[redacted]":y(e,t)]}))):"string"==typeof t?String(e).toLowerCase().includes("token")?"[redacted]":t.replace(/([?&](?:key|api_key|token)=)[^&]+/gi,"$1[redacted]"):t,g=(t,e,a,n={})=>{if(!t?.debug||"undefined"==typeof console)return;("function"==typeof console[e]?console[e]:console.log).call(console,`[plan-your-day] ${a}`,y(n))},m=t=>Array.isArray(t)?t.map((t=>String(t??""))).filter(Boolean):[],S=(t,e)=>{t&&e&&(t.textContent="",window.requestAnimationFrame((()=>{t.textContent=e})))},f=t=>o.includes(String(t||"")),b=t=>{const e=String(t||"");return s.includes(e)?e:a},h=()=>{try{const t=window.localStorage?.getItem(e);return f(t)?t:""}catch(t){return""}},w=()=>"undefined"==typeof window||"function"!=typeof window.matchMedia?null:window.matchMedia("(prefers-color-scheme: dark)"),v=(t,e=w())=>{const o=h();if(f(o))return o;const s=b(t);return s===r?e?.matches?n:a:s},M=(t,e,r,o)=>{const s=f(r)?r:a;t.setAttribute("data-plan-color-mode",s),((t,e,a)=>{t.colorModeToggle instanceof HTMLButtonElement&&(t.colorModeToggle.hidden=!1,t.colorModeToggle.setAttribute("aria-pressed",String(e===n)),t.colorModeToggle.setAttribute("aria-label",String(a.darkModeLabel||"Dark mode")),t.colorModeToggleLabel instanceof HTMLElement&&(t.colorModeToggleLabel.textContent=String(a.darkModeLabel||"Dark mode")))})(e,s,o)},L=t=>{const e=t.find((t=>t.checked));return e?e.value:""},R=t=>{const e=String(t||"");return d.includes(e)?e:""},_=(t,e)=>{const a=t.form instanceof HTMLFormElement?new FormData(t.form):new FormData;return{category:String(a.get("category")||e.category||""),category_search:String(a.get("category_search")||e.categorySearch||""),waypoints:a.getAll("waypoints[]").map((t=>String(t||""))).filter(Boolean),start_mode:String(a.get("start_mode")||e.startMode||"default"),custom_start:String(a.get("custom_start")||"")}},E=(t,e,a)=>{const n=String(t?.id||""),r=String(t?.label||""),o=String(t?.address||""),s=String(t?.distance_label||""),i=String(t?.maps_uri||""),l=e.includes(n);return`\n
\n \n
${c(r)}
\n ${s?`
${c(s)}
`:""}\n
${c(o)}
\n
\n \n \n `},A=(t,e,a=!1)=>{if(!t?.hasMoreResults)return"";const n=String(a?e.loadingMoreResults||e.moreResultsButton||"":e.moreResultsButton||"");return`\n \n \n
\n `},q=(t,e,a,n={})=>`\n \n ${((t,e,a)=>{const n=Array.isArray(t?.searchResults)?t.searchResults:[];if(0===n.length){const e=t?.resultsEmptyState||{};return`\n
\n
${c(e.heading||"")}
\n
${c(e.body||"")}
\n
\n `}return`\n
\n ${n.map((t=>E(t,e,a))).join("")}\n
\n `})(t,e,a)}\n
\n ${A(t,a,Boolean(n.isLoadingMore))}\n `,T=(t,e)=>{const a=new Set((t=>{const e=[];return(Array.isArray(t?.searchResults)?t.searchResults:[]).forEach((t=>{const a=String(t?.id||"");a&&!e.includes(a)&&e.push(a)})),e})(t));return(Array.isArray(e?.searchResults)?e.searchResults:[]).filter((t=>{const e=String(t?.id||"");return!e||!a.has(e)&&(a.add(e),!0)}))},H=(t,e)=>{t.categoryInput&&(t.categoryInput.value=e.category||""),t.waypointInputs&&(t.waypointInputs.innerHTML=m(e.route?.selectedWaypointIds).map((t=>``)).join(""))},$=(t,e)=>{if(!t.messages)return;const a=Array.isArray(e)?e:[];t.messages.hidden=0===a.length,t.messages.innerHTML=(t=>(Array.isArray(t)?t:[]).map((t=>{const e=String(t?.type||"note"),a=String(t?.text||""),n="warning"===e?"alert":"";return`${c(a)}`})).join(""))(a)},k=(t,e,a,n)=>{if(!(t instanceof HTMLElement))return;const r=t.querySelector("[data-plan-load-more-wrap]"),o=A(e,a,n);o?r instanceof HTMLElement?r.outerHTML=o:t.insertAdjacentHTML("beforeend",o):r instanceof HTMLElement&&r.remove()},x=(t,e,a,n,r={})=>{if(!(t instanceof HTMLElement))return;const o=Boolean(r.appendResults),s=Array.isArray(r.appendedResults)?r.appendedResults:[];o&&(s.length>0&&((t,e,a,n)=>{if(!(t instanceof HTMLElement&&Array.isArray(e)&&0!==e.length))return!1;const r=t.querySelector("[data-plan-results-list]");if(!(r instanceof HTMLElement))return!1;const o=t.querySelector("[data-plan-results-empty]");return o instanceof HTMLElement&&o.remove(),r.insertAdjacentHTML("beforeend",e.map((t=>E(t,a,n))).join("")),!0})(t,s,a,n)||0===s.length&&(t=>t instanceof HTMLElement&&t.querySelector("[data-plan-results-list], [data-plan-results-empty]")instanceof HTMLElement)(t))?k(t,e,n,Boolean(r.isLoadingMore)):t.innerHTML=q(e,a,n,r)},C=(t,e,a,n={})=>{const r=e.category||"",o=e.expandedCategory||"",s=m(e.route?.selectedWaypointIds),i=e.browse||{},l=Boolean(i.hasSearch)&&!r,u=l||0===t.categoryButtons.length,d=l&&Boolean(e.customResultsExpanded)||!l&&0===t.categoryButtons.length,c={appendResults:Boolean(n.appendResults),appendedResults:Array.isArray(n.appendedResults)?n.appendedResults:[],isLoadingMore:Boolean(n.isLoadingMore)};t.categoryButtons.forEach((t=>{const e=t.getAttribute("data-category-key")||"",a=e===r&&e===o,n=t.closest(".plan-your-day__category-accordion-item");t.setAttribute("aria-expanded",String(a)),n instanceof HTMLElement&&n.classList.toggle("is-expanded",a)})),t.categoryRegions.forEach((t=>{const e=t.getAttribute("data-category-key")||"",n=e===r&&e===o,l=t.querySelector("[data-plan-category-results-panel]");if(t.hidden=!n,l instanceof HTMLElement){if(!n)return void(l.innerHTML="");x(l,i,s,a,c)}})),t.customResults&&(t.customResults.hidden=!u,t.customResults.classList.toggle("is-expanded",d)),t.customResultsButton&&t.customResultsButton.setAttribute("aria-expanded",String(d)),t.customResultsRegion&&(t.customResultsRegion.hidden=!d),t.customResultsHeading&&(t.customResultsHeading.textContent=l?p(a.searchResultsFor||"",{search:i.categoryLabel||""}):String(i?.resultsEmptyState?.heading||"")),t.customResultsDescription&&(t.customResultsDescription.textContent=String(l?a.customSearchResultsDescription||"":i?.resultsEmptyState?.body||"")),t.customResultsPanel&&(d?l?x(t.customResultsPanel,i,s,a,c):t.customResultsPanel.innerHTML=q(i,s,a,{isLoadingMore:!1}):t.customResultsPanel.innerHTML="")},I=(t,e,a)=>{t.tripHeaderActions&&(t.tripHeaderActions.innerHTML=((t,e)=>{const a=m(t?.selectedWaypointIds),n=String(t?.tripCountLabel||"");return`\n ${c(n)}\n ${a.length>0?``:""}\n `})(e.route,a)),t.tripRegion&&(t.tripRegion.innerHTML=((t,e,a)=>{const n=Array.isArray(t?.tripWaypoints)?t.tripWaypoints:[];if(0===n.length){const a=t?.tripEmptyState||{};return`\n \n
${c(a.heading||e.tripEmptyHeading||"")}
\n
${c(a.body||e.tripEmptyBody||"")}
\n
\n `}return`\n \n ${n.map(((t,a)=>{const r=String(t?.id||""),o=String(t?.label||""),s=String(t?.address||""),i=a>0,l=a\n \n
${c(String(a+1))}\n
\n
\n \n \n \n \n
\n \n `})).join("")}\n
\n `})(e.route,a,t.tripRegion.getAttribute("data-plan-trip-help-id")||""))},B=(t,e,a)=>{const n=e.route||{},r=String(n.iframeSrc||""),o=n.emptyPreviewState||{},s=String(n.mapsUrl||""),i=m(n.selectedWaypointIds).length>0;$(t,n.messages),t.mapWrap&&(t.mapWrap.hidden=""===r),t.iframe&&(t.iframe.src=r),t.previewEmpty&&(t.previewEmpty.hidden=""!==r),t.previewEmptyHeading&&(t.previewEmptyHeading.textContent=String(o.heading||"")),t.previewEmptyBody&&(t.previewEmptyBody.textContent=String(o.body||"")),t.summaryCount&&(t.summaryCount.textContent=String(n.tripCountLabel||""),t.summaryCount.hidden=i),t.openLinkLabel&&(t.openLinkLabel.textContent=String(n.mapsLinkLabel||"")),t.openLink&&(t.openLink.hidden=!i,t.openLink.classList.toggle("is-disabled",""===s),s?(t.openLink.href=s,t.openLink.removeAttribute("aria-disabled"),t.openLink.removeAttribute("tabindex"),t.openLink.removeAttribute("role")):(t.openLink.removeAttribute("href"),t.openLink.setAttribute("aria-disabled","true"),t.openLink.setAttribute("tabindex","0"),t.openLink.setAttribute("role","button")))},D=t=>t instanceof HTMLElement&&"function"==typeof t.focus&&(t.focus(),document.activeElement===t),F=t=>{if(!(t instanceof HTMLButtonElement))return null;if(t.matches('[data-plan-action="add-waypoint"]'))return{action:"add-waypoint",placeId:t.getAttribute("data-place-id")||t.value||""};if(t.matches('[data-plan-action="remove-waypoint"]'))return{action:"remove-waypoint",placeId:t.getAttribute("data-place-id")||t.value||""};if(t.matches("[data-plan-clear-trip]"))return{action:"clear-trip",placeId:""};if("move_waypoint"===t.name&&t.value){const[e,a]=String(t.value).split(":",2);return{action:"move-waypoint",placeId:e||"",direction:a||""}}return null},P=t=>0===m(t?.selectedWaypointIds).length,U=(t,e,a)=>{const n=R(e);t.customStartWrap instanceof HTMLElement&&t.customStartWrap.setAttribute("data-plan-custom-start-state",n),t.customStartStatus instanceof HTMLElement&&(t.customStartStatus.textContent=((t,e)=>t===i?String(e.customStartChecking||"Checking starting address."):t===l?String(e.customStartFound||"Starting address found. Results are ready."):t===u?String(e.customStartNotFound||"Starting address was not found."):"")(n,a))},W=(t,e,a)=>{t.startModeInputs.forEach((t=>{t.checked=t.value===e.startMode})),t.customStartInput&&(t.customStartInput.value=e.customStart||"");const n="custom"===(L(t.startModeInputs)||e.startMode||"default");t.customStartWrap&&(t.customStartWrap.hidden=!n),t.customStartInput&&(t.customStartInput.disabled=!n),U(t,n?e.customStartStatus:"",a)},j=(t,e)=>{if(!(t.categorySearchInput instanceof HTMLInputElement))return;const a=String(e.categorySearch||"");t.categorySearchInput.value!==a&&(t.categorySearchInput.value=a)},K=(t,e)=>{t.classList.toggle("is-submitting",e),t.setAttribute("aria-busy",String(e))},O=(t,e,a)=>{t.forEach((t=>{if(!(t instanceof HTMLButtonElement||t instanceof HTMLInputElement))return;if(e)return t.hasAttribute(a)||t.setAttribute(a,t.disabled?"true":"false"),void(t.disabled=!0);const n=t.getAttribute(a);null!==n&&(t.disabled="true"===n,t.removeAttribute(a))}))},N=(t,e)=>{O(t.querySelectorAll("[data-plan-route-mutation]"),e,"data-plan-disabled-before-request")},G=(t,e)=>{O(t.querySelectorAll(['[data-plan-form] button[type="submit"]:not([data-plan-route-mutation])',"[data-plan-load-more-button]","[data-plan-start-toggle]","[data-plan-custom-results-button]",'input[name="start_mode"]',"[data-plan-custom-start]","[data-plan-category-search]"].join(",")),e,"data-plan-browse-disabled-before-request")},J=(t,e,a)=>{const n=e.category||"";if(t.categoryPanels.forEach((e=>{const r=e.getAttribute("data-category-key")||"",o=0===t.categoryButtons.length||r===n||!1===t.customResults?.hidden;e.setAttribute("aria-busy",String(a&&o))})),t.customResultsPanel instanceof HTMLElement){const e=!1===t.customResults?.hidden;t.customResultsPanel.setAttribute("aria-busy",String(a&&e))}t.tripRegion instanceof HTMLElement&&t.tripRegion.setAttribute("aria-busy",String(a)),t.previewCard instanceof HTMLElement&&t.previewCard.setAttribute("aria-busy",String(a))},Y=o=>{if(!(o instanceof HTMLElement)||"true"===o.dataset[t])return;o.dataset[t]="true";const s=(t=>{const e=t.querySelector("[data-plan-config]");if(!e)return{};try{return JSON.parse(e.textContent||"{}")}catch(t){return{}}})(o),l=s.strings||{},u={form:o.querySelector("[data-plan-form]"),liveRegion:o.querySelector("[data-plan-live-region]"),categoryInput:o.querySelector("[data-plan-category-input]"),waypointInputs:o.querySelector("[data-plan-waypoint-inputs]"),categoryButtons:Array.from(o.querySelectorAll("[data-plan-category-button]")),categoryItems:Array.from(o.querySelectorAll("[data-plan-category-item]")),categoryRegions:Array.from(o.querySelectorAll("[data-plan-category-region]")),categoryPanels:Array.from(o.querySelectorAll("[data-plan-category-results-panel]")),categorySearchInput:o.querySelector("[data-plan-category-search]"),customResults:o.querySelector("[data-plan-custom-results]"),customResultsButton:o.querySelector("[data-plan-custom-results-button]"),customResultsHeading:o.querySelector("[data-plan-custom-results-heading]"),customResultsDescription:o.querySelector("[data-plan-custom-results-description]"),customResultsRegion:o.querySelector("[data-plan-custom-results-region]"),customResultsPanel:o.querySelector("[data-plan-custom-results-panel]"),resultsHeading:o.querySelector("[data-plan-results-heading]"),startModeInputs:Array.from(o.querySelectorAll('input[name="start_mode"]')),customStartWrap:o.querySelector("[data-plan-custom-start-wrap]"),customStartInput:o.querySelector("[data-plan-custom-start]"),customStartStatus:o.querySelector("[data-plan-custom-start-status]"),startToggle:o.querySelector("[data-plan-start-toggle]"),startToggleLabel:o.querySelector("[data-plan-start-toggle-label]"),startPanel:o.querySelector("[data-plan-start-panel]"),colorModeToggle:o.querySelector("[data-plan-color-mode-toggle]"),colorModeToggleLabel:o.querySelector("[data-plan-color-mode-toggle-label]"),tripHeaderActions:o.querySelector("[data-plan-trip-header-actions]"),tripHeading:o.querySelector("[data-plan-trip-heading]"),tripRegion:o.querySelector("[data-plan-trip-region]"),messages:o.querySelector("[data-plan-messages]"),previewCard:o.querySelector("[data-plan-preview-card]"),mapWrap:o.querySelector("[data-plan-map-wrap]"),iframe:o.querySelector("[data-plan-iframe]"),previewEmpty:o.querySelector("[data-plan-preview-empty]"),previewEmptyHeading:o.querySelector("[data-plan-preview-empty-heading]"),previewEmptyBody:o.querySelector("[data-plan-preview-empty-body]"),summaryCount:o.querySelector("[data-plan-summary-count]"),openLink:o.querySelector("[data-plan-open-link]"),openLinkLabel:o.querySelector("[data-plan-open-link-label]")},d={category:String(s.initialState?.category||""),categorySearch:String(s.initialState?.categorySearch||""),startMode:String(s.initialState?.startMode||"default"),customStart:String(s.initialState?.customStart||""),customStartStatus:R(s.initialData?.browse?.customStartStatus||""),expandedCategory:String(s.initialState?.category||""),customResultsExpanded:Boolean(s.initialData?.browse?.isCustomSearch),isLoadingMore:!1,browse:s.initialData?.browse||{},route:s.initialData?.route||{}},c=u.form instanceof HTMLFormElement&&"string"==typeof s.rest?.browseUrl&&""!==s.rest.browseUrl&&"string"==typeof s.rest?.routeUrl&&""!==s.rest.routeUrl&&"string"==typeof s.rest?.endpointToken&&""!==s.rest.endpointToken,y=Boolean(s.hydration?.shouldHydrateOnLoad),m=b(s.colorModeDefault||o.getAttribute("data-plan-color-mode-default")),E=w();let A=!0,q=null,k="",x=0,U=null;const O="undefined"!=typeof window&&"function"==typeof window.matchMedia&&window.matchMedia("(prefers-reduced-motion: reduce)").matches;let Y=0;M(o,u,v(m,E),l),m===r&&E&&E.addEventListener("change",(()=>{h()||M(o,u,v(m,E),l)}));const z=()=>{C(u,d,l,{isLoadingMore:d.isLoadingMore}),I(u,d,l),B(u,d),H(u,d),W(u,d,l),j(u,d),N(o,!1)},Q=(t,e="")=>{$(u,[{type:"warning",text:t||l.requestFailed||""}]),S(u.liveRegion,e||t||l.requestFailed||"")},V=(t={})=>{if(!u.startToggle||!u.startPanel)return;const e=!1!==t.syncHidden;u.startToggle.hidden=!1,u.startToggle.setAttribute("aria-expanded",String(A)),u.startToggle.classList.toggle("is-collapsed",!A),e&&(u.startPanel.hidden=!A),u.startToggleLabel&&(u.startToggleLabel.textContent=String(A?l.hideStartOptions||"Hide options":l.showStartOptions||"Show options"))},X=t=>{if(!(u.startPanel instanceof HTMLElement&&u.startToggle))return A=t,void V();const e=u.startPanel,a=O?0:480;Y&&(window.cancelAnimationFrame(Y),Y=0);const n=e.hidden?0:e.getBoundingClientRect().height;e.hidden=!1,t&&(e.style.height="");const r=t?e.scrollHeight:0,o=t&&r>0?Math.max(n/r,0):1,s=t?1:0;if(A=t,e.style.overflow="hidden",e.style.pointerEvents="none",e.style.height=`${n}px`,e.style.opacity=String(o),V({syncHidden:!1}),a<=0||n===r)return e.style.height="",e.style.overflow="",e.style.pointerEvents="",e.style.opacity="",e.hidden=!t,void V();const i=performance.now(),l=u=>{const d=Math.min((u-i)/a,1),c=(p=d)<.5?4*p*p*p:1-Math.pow(-2*p+2,3)/2;var p;const y=n+(r-n)*c,g=o+(s-o)*c;e.style.height=`${Math.max(y,0)}px`,e.style.opacity=String(Math.max(Math.min(g,1),0)),d<1?Y=window.requestAnimationFrame(l):(e.style.height="",e.style.overflow="",e.style.pointerEvents="",e.style.opacity="",e.hidden=!t,V(),Y=0)};Y=window.requestAnimationFrame(l)},Z=async(t,e,a={})=>{if(!c)return"unsupported";const n=Boolean(a.appendBrowseResults),r=String(a.announcementMessage||""),y=String(a.errorMessage||""),m=String(a.searchContextKey||""),f="browse"===t&&!1!==a.refreshRoute,b="route"===t?a.routeFocusRequest??null:null,h=String(e.start_mode||""),w=String(e.custom_start||""),v="browse"===t&&!n&&"custom"===h&&""!==w.trim(),M="route"===t&&(d.customStartStatus===i||"custom"!==h||""===w.trim()||w!==String(d.customStart||""));if(q instanceof AbortController){if("route"===k)return g(s,"info","request:blocked",{endpointKey:t,blockedBy:k}),"busy";q.abort()}x+=1;const L=x;"route"===t&&(U=b?{requestId:L,focusRequest:b}:null),q=new AbortController,k=t,K(o,!0),J(u,d,!0),N(o,"route"===t),G(o,"route"===t),v?(d.customStartStatus=i,W(u,d,l)):("browse"===t&&!n||M)&&(d.customStartStatus="",W(u,d,l)),g(s,"info","request:start",{endpointKey:t,payload:e});try{const a={...e,endpoint_token:s.rest.endpointToken};"browse"===t&&(a.refresh_route=f,""!==m&&(a.search_context_key=m));const i=await fetch(s.rest["browse"===t?"browseUrl":"routeUrl"],{method:"POST",credentials:"same-origin",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify(a),signal:q.signal}),c=await i.json().catch((()=>({})));if(g(s,i.ok?"info":"warn","request:response",{endpointKey:t,status:i.status,ok:i.ok,body:c}),!i.ok)throw new Error(c?.message||l.requestFailed||"");if(L!==x)return!0;if("browse"===t){const t=d.category||"",a=d.expandedCategory||"",r=d.categorySearch||"",s=c?.browse||{};if(n&&""!==String(s.searchResultsError||""))return d.isLoadingMore=!1,C(u,d,l,{appendResults:!0,appendedResults:[],isLoadingMore:!1}),Q(y||l.requestFailed||"",y||l.requestFailed||""),"failed";const i=n&&""!==m&&m===String(d.browse?.searchContextKey||"")&&m===String(s.searchContextKey||""),g=i?T(d.browse,s):[];if(d.browse=i?{...(_=d.browse)||{},...(E=s)||{},searchResults:[...Array.isArray(_?.searchResults)?_.searchResults:[],...T(_,E)]}:s,d.route=c?.route||d.route||{},d.category=String(d.browse.categoryKey||e.category||""),d.categorySearch=String(d.browse.categorySearch||e.category_search||""),d.customStartStatus=R(d.browse.customStartStatus||""),d.isLoadingMore=!1,d.category?(d.expandedCategory=d.category===t?a:d.category,d.customResultsExpanded=!1):d.browse.hasSearch?String(e.category_search||"")!==r&&(d.expandedCategory="",d.customResultsExpanded=!0):(d.expandedCategory="",d.customResultsExpanded=!1),i)return C(u,d,l,{appendResults:!0,appendedResults:g,isLoadingMore:!1}),I(u,d,l),B(u,d),H(u,d),W(u,d,l),j(u,d),N(o,!1),S(u.liveRegion,d.browse?.searchResultsError?y||l.requestFailed||"":((t,e,a)=>t>0?p(a.loadedMoreResults||"",{count:t}):String(e?.hasMoreResults?a.resultsUpdated||"":a.noMoreResults||""))(g.length,d.browse,l)),"success"}else d.route=c?.route||d.route||{},d.category=String(d.route.categoryKey||d.category||""),d.categorySearch=String(d.route.categorySearch||e.category_search||"");return d.startMode=String(e.start_mode||d.startMode||"default"),d.customStart=String(e.custom_start||""),z(),"route"===t&&U&&U.requestId===L&&(((t,e)=>{if(!e)return;const a=String(e.placeId||""),n=t.tripHeaderActions?.querySelector("button:not([disabled]):not([hidden])");let r=null;if(a&&"add-waypoint"===e.action)r=t.tripRegion?.querySelector(`[data-waypoint-id="${a}"] button[name="remove_waypoint"]`);else if(a&&"move-waypoint"===e.action){const n=String(e.direction||"");r=t.tripRegion?.querySelector(`[data-waypoint-id="${a}"] button[name="move_waypoint"][value="${a}:${n}"]`)||t.tripRegion?.querySelector(`[data-waypoint-id="${a}"] button[name="remove_waypoint"]`)}else"remove-waypoint"===e.action?r=t.tripRegion?.querySelector('[data-plan-trip-list] button[name="remove_waypoint"]')||n:"clear-trip"===e.action&&(r=n||t.tripHeading);D(r)||D(t.tripHeading)})(u,U.focusRequest),U=null),S(u.liveRegion,r||""),"success"}catch(a){return"AbortError"===a?.name?(g(s,"info","request:aborted",{endpointKey:t}),L===x&&v&&(d.customStartStatus="",W(u,d,l)),"aborted"):L!==x?"stale":(g(s,"error","request:failed",{endpointKey:t,error:a instanceof Error?a.message:String(a||""),payload:e}),n&&(d.isLoadingMore=!1,C(u,d,l,{appendResults:!0,appendedResults:[],isLoadingMore:!1})),Q(n?y||l.requestFailed||"":a instanceof Error?a.message:l.requestFailed||"",n&&(y||l.requestFailed)||""),v&&(d.customStartStatus="",W(u,d,l)),"failed")}finally{L===x&&(n&&d.isLoadingMore&&(d.isLoadingMore=!1),K(o,!1),J(u,d,!1),N(o,!1),"route"===t&&U&&U.requestId===L&&(U=null),G(o,!1),q=null,k="")}var _,E};if(u.startModeInputs.forEach((t=>{t.addEventListener("change",(()=>{d.startMode=L(u.startModeInputs)||d.startMode||"default",W(u,d,l),c?Z("browse",_(u,d),{announcementMessage:l.startingPointUpdated||"",refreshRoute:!0}):S(u.liveRegion,l.startingPointUpdated||"")}))})),u.customStartInput instanceof HTMLInputElement&&u.customStartInput.addEventListener("change",(()=>{d.customStart=u.customStartInput.value||"",W(u,d,l),c?Z("browse",_(u,d),{announcementMessage:l.startingPointUpdated||"",refreshRoute:!0}):S(u.liveRegion,l.startingPointUpdated||"")})),u.categorySearchInput instanceof HTMLInputElement&&(u.categorySearchInput.addEventListener("input",(()=>{d.categorySearch=u.categorySearchInput.value||""})),u.categorySearchInput.addEventListener("keydown",(t=>{if("Enter"!==t.key||!c)return;t.preventDefault();const e=_(u,d);e.category="",e.category_search=u.categorySearchInput.value||"",d.expandedCategory="",d.customResultsExpanded=!0,Z("browse",e,{announcementMessage:l.resultsUpdated||"",refreshRoute:P(d.route)})}))),u.form instanceof HTMLFormElement&&u.form.addEventListener("submit",(t=>{const e=t.submitter;if(!(e instanceof HTMLButtonElement&&c))return;let a="browse",n=l.resultsUpdated||"";const r=_(u,d);if(e.matches("[data-plan-category-button]")){const a=e.getAttribute("data-category-key")||"";if(a===d.category){const n=e.querySelector(".plan-your-day__category-title")?.textContent?.trim()||a;return t.preventDefault(),d.expandedCategory=d.expandedCategory===a?"":a,d.customResultsExpanded=!1,C(u,d,l),void S(u.liveRegion,d.expandedCategory===a?p(l.categoryResultsExpanded||"",{category:n}):p(l.categoryResultsCollapsed||"",{category:n}))}r.category=a,r.category_search="",d.expandedCategory=a,d.customResultsExpanded=!1}else e.matches('[data-plan-action="search-category-query"]')?(r.category="",r.category_search=u.categorySearchInput instanceof HTMLInputElement&&u.categorySearchInput.value||"",d.expandedCategory="",d.customResultsExpanded=!0):e.matches('[data-plan-action="add-waypoint"]')?(r.waypoints=[...r.waypoints,e.getAttribute("data-place-id")||e.value||""],a="route",n=l.tripUpdated||""):e.matches('[data-plan-action="remove-waypoint"]')?(r.remove_waypoint=e.getAttribute("data-place-id")||e.value||"",a="route",n=l.tripUpdated||""):e.matches("[data-plan-clear-trip]")?(r.clear_trip=!0,a="route",n=l.tripUpdated||""):"move_waypoint"===e.name&&e.value&&(r.move_waypoint=e.value,a="route",n=l.tripUpdated||"");t.preventDefault(),Z(a,r,{announcementMessage:n,refreshRoute:"browse"===a?P(d.route):void 0,routeFocusRequest:"route"===a?F(e):null})})),o.addEventListener("click",(t=>{const r=t.target;if(!(r instanceof HTMLElement))return;if(r.closest('[data-plan-open-link][aria-disabled="true"]')instanceof HTMLElement)return t.preventDefault(),void S(u.liveRegion,l.openMapsDisabled||"");if(r.closest("[data-plan-start-toggle]")instanceof HTMLButtonElement)return t.preventDefault(),A?A&&X(!1):A||X(!0),void S(u.liveRegion,A?l.startOptionsExpanded||"":l.startOptionsCollapsed||"");if(r.closest("[data-plan-color-mode-toggle]")instanceof HTMLButtonElement){t.preventDefault();const r=(o.getAttribute("data-plan-color-mode")===n?n:a)===n?a:n;return(t=>{if(f(t))try{window.localStorage?.setItem(e,t)}catch(t){}})(r),void M(o,u,r,l)}if(r.closest("[data-plan-custom-results-button]")instanceof HTMLButtonElement){if(t.preventDefault(),!d.browse?.hasSearch||d.category)return;return d.expandedCategory="",d.customResultsExpanded=!d.customResultsExpanded,C(u,d,l),void S(u.liveRegion,d.customResultsExpanded?String(l.customResultsExpanded||""):String(l.customResultsCollapsed||""))}const s=r.closest("[data-plan-load-more-button]");if(s instanceof HTMLButtonElement){if(t.preventDefault(),!c||d.isLoadingMore||s.disabled)return;const e=String(d.browse?.nextPageToken||"");if(!e)return void S(u.liveRegion,l.noMoreResults||"");d.isLoadingMore=!0,C(u,d,l,{appendResults:!0,appendedResults:[],isLoadingMore:!0}),S(u.liveRegion,l.loadingMoreResults||""),Z("browse",{..._(u,d),page_token:e,append_results:!0},{appendBrowseResults:!0,errorMessage:l.loadMoreError||"",refreshRoute:!1,searchContextKey:String(d.browse?.searchContextKey||"")})}})),o.addEventListener("keydown",(t=>{const e=t.target;if(!(e instanceof HTMLElement))return;e.closest('[data-plan-open-link][aria-disabled="true"]')instanceof HTMLElement&&("Enter"!==t.key&&" "!==t.key||(t.preventDefault(),S(u.liveRegion,l.openMapsDisabled||"")))})),z(),V(),o.classList.add("is-enhanced"),y){if(!c)return void Q(l.requestFailed||"");Z("browse",_(u,d),{refreshRoute:!0})}},z=()=>{document.querySelectorAll("[data-plan-root]").forEach(Y)};"loading"===document.readyState?document.addEventListener("DOMContentLoaded",z,{once:!0}):z()})();
\ No newline at end of file
+(()=>{const t="planYourDayEnhanced",e="planYourDayColorMode",a="light",n="dark",r="system",o=[a,n],s=[...o,r],i="checking",l="found",u="not_found",d=[i,l,u],c=t=>String(t??"").replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'"),p=(t,e={})=>{let a=String(t||"");return Object.entries(e||{}).forEach((([t,e])=>{a=a.split(`{${t}}`).join(String(e??""))})),a},y=(t,e="")=>Array.isArray(t)?t.map((t=>y(t))):t&&"object"==typeof t?Object.fromEntries(Object.entries(t).map((([t,e])=>{const a=String(t).toLowerCase();return[t,a.includes("token")||a.includes("api_key")||a.includes("authorization")||a.includes("cookie")||a.includes("secret")?"[redacted]":y(e,t)]}))):"string"==typeof t?String(e).toLowerCase().includes("token")?"[redacted]":t.replace(/([?&](?:key|api_key|token)=)[^&]+/gi,"$1[redacted]"):t,g=(t,e,a,n={})=>{if(!t?.debug||"undefined"==typeof console)return;("function"==typeof console[e]?console[e]:console.log).call(console,`[plan-your-day] ${a}`,y(n))},m=t=>Array.isArray(t)?t.map((t=>String(t??""))).filter(Boolean):[],S=(t,e)=>{t&&e&&(t.textContent="",window.requestAnimationFrame((()=>{t.textContent=e})))},f=t=>o.includes(String(t||"")),b=t=>{const e=String(t||"");return s.includes(e)?e:a},h=()=>{try{const t=window.localStorage?.getItem(e);return f(t)?t:""}catch(t){return""}},w=()=>"undefined"==typeof window||"function"!=typeof window.matchMedia?null:window.matchMedia("(prefers-color-scheme: dark)"),v=(t,e=w())=>{const o=h();if(f(o))return o;const s=b(t);return s===r?e?.matches?n:a:s},M=(t,e,r,o)=>{const s=f(r)?r:a;t.setAttribute("data-plan-color-mode",s),((t,e,a)=>{t.colorModeToggle instanceof HTMLButtonElement&&(t.colorModeToggle.hidden=!1,t.colorModeToggle.setAttribute("aria-pressed",String(e===n)),t.colorModeToggle.setAttribute("aria-label",String(a.darkModeLabel||"Dark mode")),t.colorModeToggleLabel instanceof HTMLElement&&(t.colorModeToggleLabel.textContent=String(a.darkModeLabel||"Dark mode")))})(e,s,o)},L=t=>{const e=t.find((t=>t.checked));return e?e.value:""},R=t=>{const e=String(t||"");return d.includes(e)?e:""},E=(t,e)=>{const a=t.form instanceof HTMLFormElement?new FormData(t.form):new FormData;return{category:String(a.get("category")||e.category||""),category_search:String(a.get("category_search")||e.categorySearch||""),waypoints:a.getAll("waypoints[]").map((t=>String(t||""))).filter(Boolean),start_mode:String(a.get("start_mode")||e.startMode||"default"),custom_start:String(a.get("custom_start")||"")}},_=(t,e,a)=>{const n=String(t?.id||""),r=String(t?.label||""),o=String(t?.address||""),s=String(t?.distance_label||""),i=String(t?.maps_uri||""),l=e.includes(n);return`\n \n \n
${c(r)}
\n ${s?`
${c(s)}
`:""}\n
${c(o)}
\n
\n \n \n `},A=(t,e,a=!1)=>{if(!t?.hasMoreResults)return"";const n=String(a?e.loadingMoreResults||e.moreResultsButton||"":e.moreResultsButton||"");return`\n \n \n
\n `},q=(t,e,a,n={})=>`\n \n ${((t,e,a)=>{const n=Array.isArray(t?.searchResults)?t.searchResults:[];if(0===n.length){const e=t?.resultsEmptyState||{};return`\n
\n
${c(e.heading||"")}
\n
${c(e.body||"")}
\n
\n `}return`\n
\n ${n.map((t=>_(t,e,a))).join("")}\n
\n `})(t,e,a)}\n
\n ${A(t,a,Boolean(n.isLoadingMore))}\n `,T=(t,e)=>{const a=new Set((t=>{const e=[];return(Array.isArray(t?.searchResults)?t.searchResults:[]).forEach((t=>{const a=String(t?.id||"");a&&!e.includes(a)&&e.push(a)})),e})(t));return(Array.isArray(e?.searchResults)?e.searchResults:[]).filter((t=>{const e=String(t?.id||"");return!e||!a.has(e)&&(a.add(e),!0)}))},H=(t,e)=>{t.categoryInput&&(t.categoryInput.value=e.category||""),t.waypointInputs&&(t.waypointInputs.innerHTML=m(e.route?.selectedWaypointIds).map((t=>``)).join(""))},k=(t,e)=>{if(!t.messages)return;const a=Array.isArray(e)?e:[];t.messages.hidden=0===a.length,t.messages.innerHTML=(t=>(Array.isArray(t)?t:[]).map((t=>{const e=String(t?.type||"note"),a=String(t?.text||""),n="warning"===e?"alert":"";return`${c(a)}`})).join(""))(a)},$=(t,e,a,n)=>{if(!(t instanceof HTMLElement))return;const r=t.querySelector("[data-plan-load-more-wrap]"),o=A(e,a,n);o?r instanceof HTMLElement?r.outerHTML=o:t.insertAdjacentHTML("beforeend",o):r instanceof HTMLElement&&r.remove()},x=(t,e,a,n,r={})=>{if(!(t instanceof HTMLElement))return;const o=Boolean(r.appendResults),s=Array.isArray(r.appendedResults)?r.appendedResults:[];o&&(s.length>0&&((t,e,a,n)=>{if(!(t instanceof HTMLElement&&Array.isArray(e)&&0!==e.length))return!1;const r=t.querySelector("[data-plan-results-list]");if(!(r instanceof HTMLElement))return!1;const o=t.querySelector("[data-plan-results-empty]");return o instanceof HTMLElement&&o.remove(),r.insertAdjacentHTML("beforeend",e.map((t=>_(t,a,n))).join("")),!0})(t,s,a,n)||0===s.length&&(t=>t instanceof HTMLElement&&t.querySelector("[data-plan-results-list], [data-plan-results-empty]")instanceof HTMLElement)(t))?$(t,e,n,Boolean(r.isLoadingMore)):t.innerHTML=q(e,a,n,r)},C=(t,e,a,n={})=>{const r=e.category||"",o=e.expandedCategory||"",s=m(e.route?.selectedWaypointIds),i=e.browse||{},l=Boolean(i.hasSearch)&&!r,u=l||0===t.categoryButtons.length,d=l&&Boolean(e.customResultsExpanded)||!l&&0===t.categoryButtons.length,c={appendResults:Boolean(n.appendResults),appendedResults:Array.isArray(n.appendedResults)?n.appendedResults:[],isLoadingMore:Boolean(n.isLoadingMore)};t.categoryButtons.forEach((t=>{const e=t.getAttribute("data-category-key")||"",a=e===r&&e===o,n=t.closest(".plan-your-day__category-accordion-item");t.setAttribute("aria-expanded",String(a)),n instanceof HTMLElement&&n.classList.toggle("is-expanded",a)})),t.categoryRegions.forEach((t=>{const e=t.getAttribute("data-category-key")||"",n=e===r&&e===o,l=t.querySelector("[data-plan-category-results-panel]");if(t.hidden=!n,l instanceof HTMLElement){if(!n)return void(l.innerHTML="");x(l,i,s,a,c)}})),t.customResults&&(t.customResults.hidden=!u,t.customResults.classList.toggle("is-expanded",d)),t.customResultsButton&&t.customResultsButton.setAttribute("aria-expanded",String(d)),t.customResultsRegion&&(t.customResultsRegion.hidden=!d),t.customResultsHeading&&(t.customResultsHeading.textContent=l?p(a.searchResultsFor||"",{search:i.categoryLabel||""}):String(i?.resultsEmptyState?.heading||"")),t.customResultsDescription&&(t.customResultsDescription.textContent=String(l?a.customSearchResultsDescription||"":i?.resultsEmptyState?.body||"")),t.customResultsPanel&&(d?l?x(t.customResultsPanel,i,s,a,c):t.customResultsPanel.innerHTML=q(i,s,a,{isLoadingMore:!1}):t.customResultsPanel.innerHTML="")},I=(t,e,a)=>{t.tripHeaderActions&&(t.tripHeaderActions.innerHTML=((t,e)=>{const a=m(t?.selectedWaypointIds),n=String(t?.tripCountLabel||"");return`\n ${c(n)}\n ${a.length>0?``:""}\n `})(e.route,a)),t.tripRegion&&(t.tripRegion.innerHTML=((t,e,a)=>{const n=Array.isArray(t?.tripWaypoints)?t.tripWaypoints:[];if(0===n.length){const a=t?.tripEmptyState||{};return`\n \n
${c(a.heading||e.tripEmptyHeading||"")}
\n
${c(a.body||e.tripEmptyBody||"")}
\n
\n `}return`\n \n ${n.map(((t,a)=>{const r=String(t?.id||""),o=String(t?.label||""),s=String(t?.address||""),i=a>0,l=a\n \n
${c(String(a+1))}\n
\n
\n \n \n \n \n
\n \n `})).join("")}\n
\n `})(e.route,a,t.tripRegion.getAttribute("data-plan-trip-help-id")||""))},B=(t,e,a)=>{const n=e.route||{},r=String(n.iframeSrc||""),o=n.emptyPreviewState||{},s=String(n.mapsUrl||""),i=m(n.selectedWaypointIds).length>0;k(t,n.messages),t.mapWrap&&(t.mapWrap.hidden=""===r),t.iframe&&(t.iframe.src=r),t.previewEmpty&&(t.previewEmpty.hidden=""!==r),t.previewEmptyHeading&&(t.previewEmptyHeading.textContent=String(o.heading||"")),t.previewEmptyBody&&(t.previewEmptyBody.textContent=String(o.body||"")),t.summaryCount&&(t.summaryCount.textContent=String(n.tripCountLabel||""),t.summaryCount.hidden=i),t.openLinkLabel&&(t.openLinkLabel.textContent=String(n.mapsLinkLabel||"")),t.openLink&&(t.openLink.hidden=!i,t.openLink.classList.toggle("is-disabled",""===s),s?(t.openLink.href=s,t.openLink.removeAttribute("aria-disabled"),t.openLink.removeAttribute("tabindex"),t.openLink.removeAttribute("role")):(t.openLink.removeAttribute("href"),t.openLink.setAttribute("aria-disabled","true"),t.openLink.setAttribute("tabindex","0"),t.openLink.setAttribute("role","button")))},D=t=>t instanceof HTMLElement&&"function"==typeof t.focus&&(t.focus(),document.activeElement===t),F=t=>{if(!(t instanceof HTMLButtonElement))return null;if(t.matches('[data-plan-action="add-waypoint"]'))return{action:"add-waypoint",placeId:t.getAttribute("data-place-id")||t.value||""};if(t.matches('[data-plan-action="remove-waypoint"]'))return{action:"remove-waypoint",placeId:t.getAttribute("data-place-id")||t.value||""};if(t.matches("[data-plan-clear-trip]"))return{action:"clear-trip",placeId:""};if("move_waypoint"===t.name&&t.value){const[e,a]=String(t.value).split(":",2);return{action:"move-waypoint",placeId:e||"",direction:a||""}}return null},P=t=>0===m(t?.selectedWaypointIds).length,U=(t,e,a)=>{const n=R(e);t.customStartWrap instanceof HTMLElement&&t.customStartWrap.setAttribute("data-plan-custom-start-state",n),t.customStartStatus instanceof HTMLElement&&(t.customStartStatus.textContent=((t,e)=>t===i?String(e.customStartChecking||"Checking starting address."):t===l?String(e.customStartFound||"Starting address found. Results are ready."):t===u?String(e.customStartNotFound||"Starting address was not found."):"")(n,a))},j=(t,e,a)=>{t.startModeInputs.forEach((t=>{t.checked=t.value===e.startMode})),t.customStartInput&&(t.customStartInput.value=e.customStart||"");const n="custom"===(L(t.startModeInputs)||e.startMode||"default");t.customStartWrap&&(t.customStartWrap.hidden=!n),t.customStartInput&&(t.customStartInput.disabled=!n),U(t,n?e.customStartStatus:"",a)},W=(t,e)=>{if(!(t.categorySearchInput instanceof HTMLInputElement))return;const a=String(e.categorySearch||"");t.categorySearchInput.value!==a&&(t.categorySearchInput.value=a)},O=(t,e)=>{t.classList.toggle("is-submitting",e),t.setAttribute("aria-busy",String(e))},K=(t,e,a)=>{t.forEach((t=>{if(!(t instanceof HTMLButtonElement||t instanceof HTMLInputElement))return;if(e)return t.hasAttribute(a)||t.setAttribute(a,t.disabled?"true":"false"),void(t.disabled=!0);const n=t.getAttribute(a);null!==n&&(t.disabled="true"===n,t.removeAttribute(a))}))},N=(t,e)=>{K(t.querySelectorAll("[data-plan-route-mutation]"),e,"data-plan-disabled-before-request")},J=(t,e)=>{K(t.querySelectorAll(['[data-plan-form] button[type="submit"]:not([data-plan-route-mutation])',"[data-plan-load-more-button]","[data-plan-start-toggle]","[data-plan-custom-results-button]",'input[name="start_mode"]',"[data-plan-custom-start]","[data-plan-category-search]"].join(",")),e,"data-plan-browse-disabled-before-request")},G=(t,e,a)=>{const n=e.category||"";if(t.categoryPanels.forEach((e=>{const r=e.getAttribute("data-category-key")||"",o=0===t.categoryButtons.length||r===n||!1===t.customResults?.hidden;e.setAttribute("aria-busy",String(a&&o))})),t.customResultsPanel instanceof HTMLElement){const e=!1===t.customResults?.hidden;t.customResultsPanel.setAttribute("aria-busy",String(a&&e))}t.tripRegion instanceof HTMLElement&&t.tripRegion.setAttribute("aria-busy",String(a)),t.previewCard instanceof HTMLElement&&t.previewCard.setAttribute("aria-busy",String(a))},Y=o=>{if(!(o instanceof HTMLElement)||"true"===o.dataset[t])return;o.dataset[t]="true";const s=(t=>{const e=t.querySelector("[data-plan-config]");if(!e)return{};try{return JSON.parse(e.textContent||"{}")}catch(t){return{}}})(o),l=s.strings||{},u={form:o.querySelector("[data-plan-form]"),liveRegion:o.querySelector("[data-plan-live-region]"),categoryInput:o.querySelector("[data-plan-category-input]"),waypointInputs:o.querySelector("[data-plan-waypoint-inputs]"),categoryButtons:Array.from(o.querySelectorAll("[data-plan-category-button]")),categoryItems:Array.from(o.querySelectorAll("[data-plan-category-item]")),categoryRegions:Array.from(o.querySelectorAll("[data-plan-category-region]")),categoryPanels:Array.from(o.querySelectorAll("[data-plan-category-results-panel]")),categorySearchInput:o.querySelector("[data-plan-category-search]"),customResults:o.querySelector("[data-plan-custom-results]"),customResultsButton:o.querySelector("[data-plan-custom-results-button]"),customResultsHeading:o.querySelector("[data-plan-custom-results-heading]"),customResultsDescription:o.querySelector("[data-plan-custom-results-description]"),customResultsRegion:o.querySelector("[data-plan-custom-results-region]"),customResultsPanel:o.querySelector("[data-plan-custom-results-panel]"),resultsHeading:o.querySelector("[data-plan-results-heading]"),startModeInputs:Array.from(o.querySelectorAll('input[name="start_mode"]')),customStartWrap:o.querySelector("[data-plan-custom-start-wrap]"),customStartInput:o.querySelector("[data-plan-custom-start]"),customStartStatus:o.querySelector("[data-plan-custom-start-status]"),startToggle:o.querySelector("[data-plan-start-toggle]"),startToggleLabel:o.querySelector("[data-plan-start-toggle-label]"),startPanel:o.querySelector("[data-plan-start-panel]"),colorModeToggle:o.querySelector("[data-plan-color-mode-toggle]"),colorModeToggleLabel:o.querySelector("[data-plan-color-mode-toggle-label]"),tripHeaderActions:o.querySelector("[data-plan-trip-header-actions]"),tripHeading:o.querySelector("[data-plan-trip-heading]"),tripRegion:o.querySelector("[data-plan-trip-region]"),messages:o.querySelector("[data-plan-messages]"),previewCard:o.querySelector("[data-plan-preview-card]"),mapWrap:o.querySelector("[data-plan-map-wrap]"),iframe:o.querySelector("[data-plan-iframe]"),previewEmpty:o.querySelector("[data-plan-preview-empty]"),previewEmptyHeading:o.querySelector("[data-plan-preview-empty-heading]"),previewEmptyBody:o.querySelector("[data-plan-preview-empty-body]"),summaryCount:o.querySelector("[data-plan-summary-count]"),openLink:o.querySelector("[data-plan-open-link]"),openLinkLabel:o.querySelector("[data-plan-open-link-label]")},d={category:String(s.initialState?.category||""),categorySearch:String(s.initialState?.categorySearch||""),startMode:String(s.initialState?.startMode||"default"),customStart:String(s.initialState?.customStart||""),customStartStatus:R(s.initialData?.browse?.customStartStatus||""),expandedCategory:String(s.initialState?.category||""),customResultsExpanded:Boolean(s.initialData?.browse?.isCustomSearch),isLoadingMore:!1,browse:s.initialData?.browse||{},route:s.initialData?.route||{}},c="string"==typeof s.rest?.bootstrapUrl&&""!==s.rest.bootstrapUrl;let y="string"==typeof s.rest?.endpointToken?s.rest.endpointToken:"",m=!1,_=null;const A=u.form instanceof HTMLFormElement&&"string"==typeof s.rest?.browseUrl&&""!==s.rest.browseUrl&&"string"==typeof s.rest?.routeUrl&&""!==s.rest.routeUrl&&(""!==y||c),q=Boolean(s.hydration?.shouldHydrateOnLoad),$=b(s.colorModeDefault||o.getAttribute("data-plan-color-mode-default")),x=w();let U=!0,K=null,Y="",z=0,Q=null;const V="undefined"!=typeof window&&"function"==typeof window.matchMedia&&window.matchMedia("(prefers-reduced-motion: reduce)").matches;let X=0;M(o,u,v($,x),l),$===r&&x&&x.addEventListener("change",(()=>{h()||M(o,u,v($,x),l)}));const Z=()=>{C(u,d,l,{isLoadingMore:d.isLoadingMore}),I(u,d,l),B(u,d),H(u,d),j(u,d,l),W(u,d),N(o,!1)},tt=(t,e="")=>{k(u,[{type:"warning",text:t||l.requestFailed||""}]),S(u.liveRegion,e||t||l.requestFailed||"")},et=(t={})=>{if(!u.startToggle||!u.startPanel)return;const e=!1!==t.syncHidden;u.startToggle.hidden=!1,u.startToggle.setAttribute("aria-expanded",String(U)),u.startToggle.classList.toggle("is-collapsed",!U),e&&(u.startPanel.hidden=!U),u.startToggleLabel&&(u.startToggleLabel.textContent=String(U?l.hideStartOptions||"Hide options":l.showStartOptions||"Show options"))},at=t=>{if(!(u.startPanel instanceof HTMLElement&&u.startToggle))return U=t,void et();const e=u.startPanel,a=V?0:480;X&&(window.cancelAnimationFrame(X),X=0);const n=e.hidden?0:e.getBoundingClientRect().height;e.hidden=!1,t&&(e.style.height="");const r=t?e.scrollHeight:0,o=t&&r>0?Math.max(n/r,0):1,s=t?1:0;if(U=t,e.style.overflow="hidden",e.style.pointerEvents="none",e.style.height=`${n}px`,e.style.opacity=String(o),et({syncHidden:!1}),a<=0||n===r)return e.style.height="",e.style.overflow="",e.style.pointerEvents="",e.style.opacity="",e.hidden=!t,void et();const i=performance.now(),l=u=>{const d=Math.min((u-i)/a,1),c=(p=d)<.5?4*p*p*p:1-Math.pow(-2*p+2,3)/2;var p;const y=n+(r-n)*c,g=o+(s-o)*c;e.style.height=`${Math.max(y,0)}px`,e.style.opacity=String(Math.max(Math.min(g,1),0)),d<1?X=window.requestAnimationFrame(l):(e.style.height="",e.style.overflow="",e.style.pointerEvents="",e.style.opacity="",e.hidden=!t,et(),X=0)};X=window.requestAnimationFrame(l)},nt=async(t,e,a={})=>{if(!A)return"unsupported";const n=Boolean(a.appendBrowseResults),r=String(a.announcementMessage||""),f=String(a.errorMessage||""),b=String(a.searchContextKey||""),h="browse"===t&&!1!==a.refreshRoute,w="route"===t?a.routeFocusRequest??null:null,v=String(e.start_mode||""),M=String(e.custom_start||""),L="browse"===t&&!n&&"custom"===v&&""!==M.trim(),E="route"===t&&(d.customStartStatus===i||"custom"!==v||""===M.trim()||M!==String(d.customStart||""));if(K instanceof AbortController){if("route"===Y)return g(s,"info","request:blocked",{endpointKey:t,blockedBy:Y}),"busy";K.abort()}z+=1;const q=z;"route"===t&&(Q=w?{requestId:q,focusRequest:w}:null),K=new AbortController,Y=t,O(o,!0),G(u,d,!0),N(o,"route"===t),J(o,"route"===t),L?(d.customStartStatus=i,j(u,d,l)):("browse"===t&&!n||E)&&(d.customStartStatus="",j(u,d,l)),g(s,"info","request:start",{endpointKey:t,payload:e});try{const a=await(async()=>c?m&&""!==y?y:_||(_=fetch(s.rest.bootstrapUrl,{method:"POST",credentials:"same-origin",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify({})}).then((async t=>{const e=await t.json().catch((()=>({})));if(g(s,t.ok?"info":"warn","request:bootstrap",{status:t.status,ok:t.ok,body:e}),!t.ok)throw new Error(e?.message||l.requestFailed||"");const a=String(e?.endpointToken||"");if(""===a)throw new Error(l.requestFailed||"");return y=a,m=!0,y})).finally((()=>{_=null})),_):y)();if(""===a)throw new Error(l.requestFailed||"");const i={...e,endpoint_token:a};"browse"===t&&(i.refresh_route=h,""!==b&&(i.search_context_key=b));const w=await fetch(s.rest["browse"===t?"browseUrl":"routeUrl"],{method:"POST",credentials:"same-origin",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify(i),signal:K.signal}),v=await w.json().catch((()=>({})));if(g(s,w.ok?"info":"warn","request:response",{endpointKey:t,status:w.status,ok:w.ok,body:v}),!w.ok)throw new Error(v?.message||l.requestFailed||"");if(q!==z)return!0;if("browse"===t){const t=d.category||"",a=d.expandedCategory||"",r=d.categorySearch||"",s=v?.browse||{};if(n&&""!==String(s.searchResultsError||""))return d.isLoadingMore=!1,C(u,d,l,{appendResults:!0,appendedResults:[],isLoadingMore:!1}),tt(f||l.requestFailed||"",f||l.requestFailed||""),"failed";const i=n&&""!==b&&b===String(d.browse?.searchContextKey||"")&&b===String(s.searchContextKey||""),c=i?T(d.browse,s):[];if(d.browse=i?{...(k=d.browse)||{},...($=s)||{},searchResults:[...Array.isArray(k?.searchResults)?k.searchResults:[],...T(k,$)]}:s,d.route=v?.route||d.route||{},d.category=String(d.browse.categoryKey||e.category||""),d.categorySearch=String(d.browse.categorySearch||e.category_search||""),d.customStartStatus=R(d.browse.customStartStatus||""),d.isLoadingMore=!1,d.category?(d.expandedCategory=d.category===t?a:d.category,d.customResultsExpanded=!1):d.browse.hasSearch?String(e.category_search||"")!==r&&(d.expandedCategory="",d.customResultsExpanded=!0):(d.expandedCategory="",d.customResultsExpanded=!1),i)return C(u,d,l,{appendResults:!0,appendedResults:c,isLoadingMore:!1}),I(u,d,l),B(u,d),H(u,d),j(u,d,l),W(u,d),N(o,!1),S(u.liveRegion,d.browse?.searchResultsError?f||l.requestFailed||"":((t,e,a)=>t>0?p(a.loadedMoreResults||"",{count:t}):String(e?.hasMoreResults?a.resultsUpdated||"":a.noMoreResults||""))(c.length,d.browse,l)),"success"}else d.route=v?.route||d.route||{},d.category=String(d.route.categoryKey||d.category||""),d.categorySearch=String(d.route.categorySearch||e.category_search||"");return d.startMode=String(e.start_mode||d.startMode||"default"),d.customStart=String(e.custom_start||""),Z(),"route"===t&&Q&&Q.requestId===q&&(((t,e)=>{if(!e)return;const a=String(e.placeId||""),n=t.tripHeaderActions?.querySelector("button:not([disabled]):not([hidden])");let r=null;if(a&&"add-waypoint"===e.action)r=t.tripRegion?.querySelector(`[data-waypoint-id="${a}"] button[name="remove_waypoint"]`);else if(a&&"move-waypoint"===e.action){const n=String(e.direction||"");r=t.tripRegion?.querySelector(`[data-waypoint-id="${a}"] button[name="move_waypoint"][value="${a}:${n}"]`)||t.tripRegion?.querySelector(`[data-waypoint-id="${a}"] button[name="remove_waypoint"]`)}else"remove-waypoint"===e.action?r=t.tripRegion?.querySelector('[data-plan-trip-list] button[name="remove_waypoint"]')||n:"clear-trip"===e.action&&(r=n||t.tripHeading);D(r)||D(t.tripHeading)})(u,Q.focusRequest),Q=null),S(u.liveRegion,r||""),"success"}catch(a){return"AbortError"===a?.name?(g(s,"info","request:aborted",{endpointKey:t}),q===z&&L&&(d.customStartStatus="",j(u,d,l)),"aborted"):q!==z?"stale":(g(s,"error","request:failed",{endpointKey:t,error:a instanceof Error?a.message:String(a||""),payload:e}),n&&(d.isLoadingMore=!1,C(u,d,l,{appendResults:!0,appendedResults:[],isLoadingMore:!1})),tt(n?f||l.requestFailed||"":a instanceof Error?a.message:l.requestFailed||"",n&&(f||l.requestFailed)||""),L&&(d.customStartStatus="",j(u,d,l)),"failed")}finally{q===z&&(n&&d.isLoadingMore&&(d.isLoadingMore=!1),O(o,!1),G(u,d,!1),N(o,!1),"route"===t&&Q&&Q.requestId===q&&(Q=null),J(o,!1),K=null,Y="")}var k,$};if(u.startModeInputs.forEach((t=>{t.addEventListener("change",(()=>{d.startMode=L(u.startModeInputs)||d.startMode||"default",j(u,d,l),A?nt("browse",E(u,d),{announcementMessage:l.startingPointUpdated||"",refreshRoute:!0}):S(u.liveRegion,l.startingPointUpdated||"")}))})),u.customStartInput instanceof HTMLInputElement&&u.customStartInput.addEventListener("change",(()=>{d.customStart=u.customStartInput.value||"",j(u,d,l),A?nt("browse",E(u,d),{announcementMessage:l.startingPointUpdated||"",refreshRoute:!0}):S(u.liveRegion,l.startingPointUpdated||"")})),u.categorySearchInput instanceof HTMLInputElement&&(u.categorySearchInput.addEventListener("input",(()=>{d.categorySearch=u.categorySearchInput.value||""})),u.categorySearchInput.addEventListener("keydown",(t=>{if("Enter"!==t.key||!A)return;t.preventDefault();const e=E(u,d);e.category="",e.category_search=u.categorySearchInput.value||"",d.expandedCategory="",d.customResultsExpanded=!0,nt("browse",e,{announcementMessage:l.resultsUpdated||"",refreshRoute:P(d.route)})}))),u.form instanceof HTMLFormElement&&u.form.addEventListener("submit",(t=>{const e=t.submitter;if(!(e instanceof HTMLButtonElement&&A))return;let a="browse",n=l.resultsUpdated||"";const r=E(u,d);if(e.matches("[data-plan-category-button]")){const a=e.getAttribute("data-category-key")||"";if(a===d.category){const n=e.querySelector(".plan-your-day__category-title")?.textContent?.trim()||a;return t.preventDefault(),d.expandedCategory=d.expandedCategory===a?"":a,d.customResultsExpanded=!1,C(u,d,l),void S(u.liveRegion,d.expandedCategory===a?p(l.categoryResultsExpanded||"",{category:n}):p(l.categoryResultsCollapsed||"",{category:n}))}r.category=a,r.category_search="",d.expandedCategory=a,d.customResultsExpanded=!1}else e.matches('[data-plan-action="search-category-query"]')?(r.category="",r.category_search=u.categorySearchInput instanceof HTMLInputElement&&u.categorySearchInput.value||"",d.expandedCategory="",d.customResultsExpanded=!0):e.matches('[data-plan-action="add-waypoint"]')?(r.waypoints=[...r.waypoints,e.getAttribute("data-place-id")||e.value||""],a="route",n=l.tripUpdated||""):e.matches('[data-plan-action="remove-waypoint"]')?(r.remove_waypoint=e.getAttribute("data-place-id")||e.value||"",a="route",n=l.tripUpdated||""):e.matches("[data-plan-clear-trip]")?(r.clear_trip=!0,a="route",n=l.tripUpdated||""):"move_waypoint"===e.name&&e.value&&(r.move_waypoint=e.value,a="route",n=l.tripUpdated||"");t.preventDefault(),nt(a,r,{announcementMessage:n,refreshRoute:"browse"===a?P(d.route):void 0,routeFocusRequest:"route"===a?F(e):null})})),o.addEventListener("click",(t=>{const r=t.target;if(!(r instanceof HTMLElement))return;if(r.closest('[data-plan-open-link][aria-disabled="true"]')instanceof HTMLElement)return t.preventDefault(),void S(u.liveRegion,l.openMapsDisabled||"");if(r.closest("[data-plan-start-toggle]")instanceof HTMLButtonElement)return t.preventDefault(),U?U&&at(!1):U||at(!0),void S(u.liveRegion,U?l.startOptionsExpanded||"":l.startOptionsCollapsed||"");if(r.closest("[data-plan-color-mode-toggle]")instanceof HTMLButtonElement){t.preventDefault();const r=(o.getAttribute("data-plan-color-mode")===n?n:a)===n?a:n;return(t=>{if(f(t))try{window.localStorage?.setItem(e,t)}catch(t){}})(r),void M(o,u,r,l)}if(r.closest("[data-plan-custom-results-button]")instanceof HTMLButtonElement){if(t.preventDefault(),!d.browse?.hasSearch||d.category)return;return d.expandedCategory="",d.customResultsExpanded=!d.customResultsExpanded,C(u,d,l),void S(u.liveRegion,d.customResultsExpanded?String(l.customResultsExpanded||""):String(l.customResultsCollapsed||""))}const s=r.closest("[data-plan-load-more-button]");if(s instanceof HTMLButtonElement){if(t.preventDefault(),!A||d.isLoadingMore||s.disabled)return;const e=String(d.browse?.nextPageToken||"");if(!e)return void S(u.liveRegion,l.noMoreResults||"");d.isLoadingMore=!0,C(u,d,l,{appendResults:!0,appendedResults:[],isLoadingMore:!0}),S(u.liveRegion,l.loadingMoreResults||""),nt("browse",{...E(u,d),page_token:e,append_results:!0},{appendBrowseResults:!0,errorMessage:l.loadMoreError||"",refreshRoute:!1,searchContextKey:String(d.browse?.searchContextKey||"")})}})),o.addEventListener("keydown",(t=>{const e=t.target;if(!(e instanceof HTMLElement))return;e.closest('[data-plan-open-link][aria-disabled="true"]')instanceof HTMLElement&&("Enter"!==t.key&&" "!==t.key||(t.preventDefault(),S(u.liveRegion,l.openMapsDisabled||"")))})),Z(),et(),o.classList.add("is-enhanced"),q){if(!A)return void tt(l.requestFailed||"");nt("browse",E(u,d),{refreshRoute:!0})}},z=()=>{document.querySelectorAll("[data-plan-root]").forEach(Y)};"loading"===document.readyState?document.addEventListener("DOMContentLoaded",z,{once:!0}):z()})();
\ No newline at end of file
diff --git a/plugin/plan-your-day/src/Frontend/PlannerRenderer.php b/plugin/plan-your-day/src/Frontend/PlannerRenderer.php
index 81b7890..29d8179 100644
--- a/plugin/plan-your-day/src/Frontend/PlannerRenderer.php
+++ b/plugin/plan-your-day/src/Frontend/PlannerRenderer.php
@@ -8,7 +8,6 @@
use Acodebeard\PlanYourDay\Planner\PlannerStateBuilder;
use Acodebeard\PlanYourDay\Planner\RequestStateParser;
use Acodebeard\PlanYourDay\Rest\PlannerRoutes;
-use Acodebeard\PlanYourDay\Security\VisitorTokenManager;
use Acodebeard\PlanYourDay\Settings\Settings;
defined( 'ABSPATH' ) || exit;
@@ -19,22 +18,19 @@ final class PlannerRenderer {
private RequestStateParser $request_state_parser;
private PlannerStateBuilder $planner_state_builder;
private PlannerPayloadBuilder $planner_payload_builder;
- private VisitorTokenManager $visitor_token_manager;
public function __construct(
Settings $settings,
CategoryCatalog $category_catalog,
RequestStateParser $request_state_parser,
PlannerStateBuilder $planner_state_builder,
- PlannerPayloadBuilder $planner_payload_builder,
- VisitorTokenManager $visitor_token_manager
+ PlannerPayloadBuilder $planner_payload_builder
) {
$this->settings = $settings;
$this->category_catalog = $category_catalog;
$this->request_state_parser = $request_state_parser;
$this->planner_state_builder = $planner_state_builder;
$this->planner_payload_builder = $planner_payload_builder;
- $this->visitor_token_manager = $visitor_token_manager;
}
public function render( array $request = [], string $action_url = '' ): string {
@@ -67,7 +63,6 @@ public function render( array $request = [], string $action_url = '' ): string {
$action_url = '' !== $action_url ? $action_url : $this->get_current_url();
$form_action = $action_url . '#' . $instance_id;
$maps_link_enabled = '' !== $planner_state['maps_url'];
- $endpoint_token = $this->visitor_token_manager->get_endpoint_token();
$color_mode_default = $this->settings->get_color_mode_default();
$initial_color_mode = Settings::COLOR_MODE_SYSTEM === $color_mode_default ? '' : $color_mode_default;
@@ -126,7 +121,7 @@ class="plan-your-day__layout"
-
+
planner_payload_builder->build_browse_payload( $planner_state );
$route_payload = $this->planner_payload_builder->build_route_payload( $planner_state );
@@ -697,14 +692,15 @@ private function build_config( string $instance_id, string $action_url, array $p
'actionUrl' => $action_url,
'sectionId' => $instance_id,
'colorModeDefault' => $this->settings->get_color_mode_default(),
- 'startPoints' => $start_points,
- 'categoryCatalog' => $category_catalog,
- 'rest' => [
- 'browseUrl' => rest_url( PlannerRoutes::REST_NAMESPACE . '/browse' ),
- 'routeUrl' => rest_url( PlannerRoutes::REST_NAMESPACE . '/route' ),
- 'endpointToken' => $endpoint_token,
+ 'startPoints' => $start_points,
+ 'categoryCatalog' => $category_catalog,
+ 'rest' => [
+ 'bootstrapUrl' => rest_url( PlannerRoutes::REST_NAMESPACE . '/bootstrap' ),
+ 'browseUrl' => rest_url( PlannerRoutes::REST_NAMESPACE . '/browse' ),
+ 'routeUrl' => rest_url( PlannerRoutes::REST_NAMESPACE . '/route' ),
+ 'endpointToken' => '',
],
- 'hydration' => [
+ 'hydration' => [
'shouldHydrateOnLoad' => $should_hydrate_on_load,
],
'strings' => [
diff --git a/plugin/plan-your-day/src/Plugin.php b/plugin/plan-your-day/src/Plugin.php
index 470ee20..a48101a 100644
--- a/plugin/plan-your-day/src/Plugin.php
+++ b/plugin/plan-your-day/src/Plugin.php
@@ -89,8 +89,7 @@ private function __construct() {
$this->category_catalog,
$this->request_state_parser,
$this->planner_state_builder(),
- $this->planner_payload_builder,
- $this->visitor_token_manager
+ $this->planner_payload_builder
);
$this->planner_shortcode = new PlannerShortcode( $this->planner_renderer, $this->frontend_assets );
$this->planner_block = new PlannerBlock( $this->planner_renderer, $this->frontend_assets );
diff --git a/plugin/plan-your-day/src/Rest/PlannerRoutes.php b/plugin/plan-your-day/src/Rest/PlannerRoutes.php
index ceadce7..9eb56d2 100644
--- a/plugin/plan-your-day/src/Rest/PlannerRoutes.php
+++ b/plugin/plan-your-day/src/Rest/PlannerRoutes.php
@@ -56,6 +56,16 @@ public function __construct(
}
public function register(): void {
+ register_rest_route(
+ self::REST_NAMESPACE,
+ '/bootstrap',
+ [
+ 'methods' => WP_REST_Server::CREATABLE,
+ 'callback' => [ $this, 'bootstrap' ],
+ 'permission_callback' => '__return_true',
+ ]
+ );
+
register_rest_route(
self::REST_NAMESPACE,
'/browse',
@@ -79,6 +89,32 @@ public function register(): void {
);
}
+ public function bootstrap( WP_REST_Request $request ): WP_REST_Response|WP_Error {
+ $guard = $this->guard_bootstrap_request();
+
+ if ( $guard instanceof WP_Error ) {
+ return $guard;
+ }
+
+ $endpoint_token = $this->visitor_token_manager->get_endpoint_token();
+
+ if ( '' === $endpoint_token ) {
+ return new WP_Error(
+ 'plan_your_day_token_unavailable',
+ $this->request_verification_failed_message(),
+ [
+ 'status' => 403,
+ ]
+ );
+ }
+
+ return new WP_REST_Response(
+ [
+ 'endpointToken' => $endpoint_token,
+ ]
+ );
+ }
+
public function browse( WP_REST_Request $request ): WP_REST_Response|WP_Error {
DebugLogger::log(
'rest.browse.request',
@@ -180,6 +216,20 @@ public function route( WP_REST_Request $request ): WP_REST_Response|WP_Error {
);
}
+ private function guard_bootstrap_request(): ?WP_Error {
+ if ( ! $this->request_origin_validator->is_same_site_request( $_SERVER ) ) {
+ return new WP_Error(
+ 'plan_your_day_invalid_origin',
+ $this->request_verification_failed_message(),
+ [
+ 'status' => 403,
+ ]
+ );
+ }
+
+ return $this->rate_limiter->enforce( 'bootstrap', $_SERVER, self::RATE_LIMIT_BASE_COST );
+ }
+
private function guard_request( WP_REST_Request $request, string $scope, array $request_state ): ?WP_Error {
if ( ! $this->request_origin_validator->is_same_site_request( $_SERVER ) ) {
$error = new WP_Error(
diff --git a/plugin/plan-your-day/tests/PlannerBlockTest.php b/plugin/plan-your-day/tests/PlannerBlockTest.php
index aa41a48..c7a7f0a 100644
--- a/plugin/plan-your-day/tests/PlannerBlockTest.php
+++ b/plugin/plan-your-day/tests/PlannerBlockTest.php
@@ -132,7 +132,6 @@ function wp_unique_id( string $prefix = '' ): string {
use Acodebeard\PlanYourDay\Planner\StartContextResolver;
use Acodebeard\PlanYourDay\Planner\WaypointList;
use Acodebeard\PlanYourDay\Security\RequestOriginValidator;
- use Acodebeard\PlanYourDay\Security\VisitorTokenManager;
use Acodebeard\PlanYourDay\Settings\Settings;
use PHPUnit\Framework\TestCase;
@@ -224,6 +223,10 @@ public function test_render_enqueues_frontend_assets_and_uses_shared_renderer_ou
self::assertStringContainsString( 'class="plan-your-day"', $output );
self::assertStringContainsString( 'data-plan-color-mode-default="dark"', $output );
self::assertStringContainsString( '"colorModeDefault":"dark"', $output );
+ self::assertStringContainsString( '"bootstrapUrl":"https:\/\/example.test\/wp-json\/plan-your-day\/v1\/bootstrap"', $output );
+ self::assertStringContainsString( '"endpointToken":""', $output );
+ self::assertStringNotContainsString( 'plan_your_day_visitor', $output );
+ self::assertStringNotContainsString( hash_hmac( 'sha256', str_repeat( 'ab', 24 ), 'tests-auth|plan-your-day' ), $output );
self::assertStringContainsString( 'action="https://example.test/planner#plan-your-day-1"', $output );
self::assertStringNotContainsString( 'Editable starting point helper.', $output );
self::assertStringNotContainsString( 'Editable custom start label', $output );
@@ -287,8 +290,7 @@ private function build_renderer(): PlannerRenderer {
$category_catalog,
$request_state_parser,
$planner_state_builder,
- new PlannerPayloadBuilder( $settings ),
- new VisitorTokenManager()
+ new PlannerPayloadBuilder( $settings )
);
}
}
diff --git a/plugin/plan-your-day/tests/PlannerRoutesTest.php b/plugin/plan-your-day/tests/PlannerRoutesTest.php
index a860f0c..56c6d79 100644
--- a/plugin/plan-your-day/tests/PlannerRoutesTest.php
+++ b/plugin/plan-your-day/tests/PlannerRoutesTest.php
@@ -204,6 +204,46 @@ public function test_route_rate_limiter_uses_trusted_proxy_forwarded_client_ip()
self::assertSame( 'plan_your_day_rate_limited', $second->get_error_code() );
}
+ public function test_bootstrap_returns_endpoint_token_for_same_site_request(): void {
+ $visitor_token = str_repeat( 'ab', 24 );
+ $routes = $this->build_routes( 10 );
+ $request = new WP_REST_Request( 'POST', '/plan-your-day/v1/bootstrap' );
+
+ $_SERVER = $this->same_site_server( '198.51.100.10' );
+ $_COOKIE['plan_your_day_visitor'] = $visitor_token;
+
+ $response = $routes->bootstrap( $request );
+
+ self::assertInstanceOf( WP_REST_Response::class, $response );
+ self::assertSame(
+ [
+ 'endpointToken' => hash_hmac( 'sha256', $visitor_token, 'tests-auth|plan-your-day' ),
+ ],
+ $response->get_data()
+ );
+ }
+
+ public function test_bootstrap_rejects_cross_site_request_before_token_creation(): void {
+ $routes = $this->build_routes( 10 );
+ $request = new WP_REST_Request( 'POST', '/plan-your-day/v1/bootstrap' );
+
+ $_SERVER = $this->same_site_server(
+ '198.51.100.10',
+ [
+ 'HTTP_ORIGIN' => 'https://evil.example',
+ 'HTTP_REFERER' => 'https://evil.example/planner',
+ 'HTTP_SEC_FETCH_SITE' => 'cross-site',
+ ]
+ );
+
+ $response = $routes->bootstrap( $request );
+
+ self::assertInstanceOf( WP_Error::class, $response );
+ self::assertSame( 'plan_your_day_invalid_origin', $response->get_error_code() );
+ self::assertSame( 403, $response->get_error_data()['status'] ?? null );
+ self::assertArrayNotHasKey( 'plan_your_day_visitor', $_COOKIE );
+ }
+
public function test_browse_append_results_uses_cached_search_context_ids_and_skips_route_refresh(): void {
$google_api_client = new PlannerRoutesGoogleApiClient(
GoogleApiResult::success(
diff --git a/plugin/plan-your-day/tests/browser-app/router.php b/plugin/plan-your-day/tests/browser-app/router.php
index a074254..c3668d6 100644
--- a/plugin/plan-your-day/tests/browser-app/router.php
+++ b/plugin/plan-your-day/tests/browser-app/router.php
@@ -62,6 +62,10 @@
plan_your_day_browser_render_page( 'plain' );
return;
+ case '/wp-json/plan-your-day/v1/bootstrap':
+ plan_your_day_browser_dispatch_rest( 'bootstrap' );
+ return;
+
case '/wp-json/plan-your-day/v1/browse':
plan_your_day_browser_dispatch_rest( 'browse' );
return;
@@ -109,8 +113,7 @@ function plan_your_day_browser_app(): array {
$category_catalog,
$request_state_parser,
$planner_state_builder,
- $planner_payload_builder,
- $visitor_token_manager
+ $planner_payload_builder
);
$planner_routes = new PlannerRoutes(
@@ -192,9 +195,13 @@ function plan_your_day_browser_dispatch_rest( string $route_name ): void {
$request->set_param( (string) $key, $value );
}
- $result = 'browse' === $route_name
- ? $app['routes']->browse( $request )
- : $app['routes']->route( $request );
+ if ( 'bootstrap' === $route_name ) {
+ $result = $app['routes']->bootstrap( $request );
+ } elseif ( 'browse' === $route_name ) {
+ $result = $app['routes']->browse( $request );
+ } else {
+ $result = $app['routes']->route( $request );
+ }
if ( $result instanceof WP_Error ) {
plan_your_day_browser_send_json(