1import { FranklinBlock, getAvailableChildrenRow } from '../../scripts/lib-franklin.js'; 2import { getFormData, getFormsEnv, FORMS_API_ENDPOINT_PROD, FORMS_API_ENDPOINT_UAT } from '../../scripts/lib-franklin/forms-service.js'; 3 4const CONFIG_TOKEN_PROD_DEFAULT = 'paxlovid-pharma-finder-generic-prod'; 5const CONFIG_TOKEN_UAT_DEFAULT = 'paxlovid-pharma-finder-generic'; 6 7const DEFAULT_REQUESTED_SERVICE = 'PharmacyFinder'; 8 9const isUat = () => getFormsEnv() !== 'production'; 10 11const requestPharmacyFinder = async (token, requestedService) => { 12 const { csrfToken } = await getFormData(token); 13 14 const endpoint = isUat() ? FORMS_API_ENDPOINT_UAT : FORMS_API_ENDPOINT_PROD; 15 16 const resp = await fetch(endpoint, { 17 method: 'POST', 18 cache: 'no-cache', 19 headers: { 20 'Content-Type': 'application/json', 21 'x-config-token': token, 22 }, 23 body: JSON.stringify({ 24 requestedServices: [requestedService], 25 csrfToken, 26 }), 27 }); 28 29 if (resp.status >= 400) { 30 throw new Error(`Error loading pharmacy finder: ${token}`); 31 } 32 33 return resp.json(); 34}; 35 36export default class FindPharmacy extends FranklinBlock { 37 constructor(blockName, block) { 38 super(blockName, block); 39 this.config = {}; 40 } 41 42 async beforeBlockDataRead() { 43 const read = (name) => { 44 const row = getAvailableChildrenRow(this.block, name); 45 return row ? row.textContent.trim() : null; 46 }; 47 48 this.config = { 49 heading: read('heading'), 50 requestedService: read('requested_service') || DEFAULT_REQUESTED_SERVICE, 51 configTokenProd: read('config_token_prod') || CONFIG_TOKEN_PROD_DEFAULT, 52 configTokenUat: read('config_token_uat') || CONFIG_TOKEN_UAT_DEFAULT, 53 formEndpointProd: read('form_endpoint_prod') || FORMS_API_ENDPOINT_PROD, 54 formEndpointUat: read('form_endpoint_uat') || FORMS_API_ENDPOINT_UAT, 55 esVariant: read('es_variant') === 'true', 56 }; 57 } 58 59 resolveToken() { 60 return isUat() ? this.config.configTokenUat : this.config.configTokenProd; 61 } 62 63 renderIframe(iframeSrc) { 64 const wrapper = document.createElement('div'); 65 wrapper.classList.add('core-find-pharmacy-iframe-wrapper'); 66 67 const iframe = document.createElement('iframe'); 68 iframe.classList.add('core-find-pharmacy-iframe'); 69 iframe.setAttribute('title', this.config.heading || 'Find a Pharmacy'); 70 iframe.setAttribute('loading', 'lazy'); 71 iframe.src = iframeSrc; 72 73 wrapper.append(iframe); 74 return { wrapper, iframe }; 75 } 76 77 // The embedded tool reports its own height; mirror it onto the iframe so there is no inner scrollbar. 78 static listenForContentHeight(iframe, iframeSrc) { 79 const iframeOrigin = (() => { 80 try { 81 return new URL(iframeSrc, window.location.href).origin; 82 } catch { 83 return null; 84 } 85 })(); 86 87 window.addEventListener('message', (event) => { 88 if (iframeOrigin && event.origin !== iframeOrigin) return; 89 const { messageType, height } = event.data || {}; 90 if (messageType === 'CONTENT_HEIGHT_UPDATED' && height) { 91 iframe.style.height = `${height}px`; 92 } 93 }); 94 } 95 96 async afterBlockRender() { 97 this.block.textContent = ''; 98 99 if (this.config.esVariant) { 100 this.block.classList.add('es'); 101 } 102 103 if (this.config.heading) { 104 const heading = document.createElement('h2'); 105 heading.classList.add('core-find-pharmacy-heading'); 106 heading.textContent = this.config.heading; 107 this.block.append(heading); 108 } 109 110 try { 111 const token = this.resolveToken(); 112 const response = await requestPharmacyFinder(token, this.config.requestedService); 113 const iframeSrc = response?.data?.[0]?.details?.url; 114 115 if (!iframeSrc) { 116 throw new Error('Pharmacy finder response did not include an iframe source'); 117 } 118 119 const { wrapper, iframe } = this.renderIframe(iframeSrc); 120 this.block.append(wrapper); 121 FindPharmacy.listenForContentHeight(iframe, iframeSrc); 122 } catch (error) { 123 console.error('Error loading pharmacy finder', error); 124 this.block.classList.add('find-pharmacy-error'); 125 const message = document.createElement('p'); 126 message.textContent = error.message; 127 this.block.append(message); 128 } 129 130 this.block.classList.add('block-padding-standard'); 131 this.block.classList.add('block-focus-visible'); 132 } 133}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.