1/* docs/components/marketplace/marketplace-intake.js 2 * Marketplace token intake, OAuth redirect, and subscription activation. 3 * IIFE pattern â mounts into #marketplace-intake-mount. 4 */ 5(function () { 6 'use strict'; 7 8 window.HAIPHEN = window.HAIPHEN || {}; 9 10 var PROVIDER_LABELS = { 11 gcp: 'Google Cloud Marketplace', 12 aws: 'AWS Marketplace', 13 azure: 'Azure Marketplace', 14 }; 15 16 var RESOLVE_ENDPOINTS = { 17 gcp: 'https://checkout.haiphen.io/v1/marketplace/gcp/signup', 18 aws: 'https://checkout.haiphen.io/v1/marketplace/aws/resolve', 19 azure: 'https://checkout.haiphen.io/v1/marketplace/azure/resolve', 20 }; 21 22 // Store marketplace tokens in memory only (never sessionStorage) to prevent XSS exfiltration 23 var _mktToken = null; 24 var _mktProvider = null; 25 26 function getStoredToken() { return _mktToken; } 27 function getStoredProvider() { return _mktProvider; } 28 function clearStored() { _mktToken = null; _mktProvider = null; } 29 30 function providerLabel(provider) { 31 return PROVIDER_LABELS[provider] || provider || 'Marketplace'; 32 } 33 34 function renderConnecting(mount, provider) { 35 var label = providerLabel(provider); 36 mount.innerHTML = [ 37 '<div class="mkt-intake">', 38 ' <div class="mkt-intake-icon">', 39 ' <svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">', 40 ' <circle cx="24" cy="24" r="22" stroke="#5A9BD4" stroke-width="2"/>', 41 ' <path d="M16 24h16M24 16l8 8-8 8" stroke="#5A9BD4" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>', 42 ' </svg>', 43 ' </div>', 44 ' <h2 class="mkt-intake-title">Connecting to ' + label + '</h2>', 45 ' <p class="mkt-intake-desc">Sign in to your Haiphen account to complete your marketplace subscription setup.</p>', 46 ' <button class="btn btn-primary mkt-signin-btn" id="mkt-signin-btn">Sign in to Haiphen</button>', 47 ' <p class="mkt-intake-note">You will be redirected back here automatically after signing in.</p>', 48 '</div>', 49 ].join('\n'); 50 51 var btn = mount.querySelector('#mkt-signin-btn'); 52 if (btn) { 53 btn.addEventListener('click', function () { 54 var returnTo = 'https://haiphen.io/marketplace?activate=1'; 55 if (window.HAIPHEN && window.HAIPHEN.AuthSession && typeof window.HAIPHEN.AuthSession.loginUrl === 'function') { 56 window.location.assign(window.HAIPHEN.AuthSession.loginUrl(returnTo)); 57 } else { 58 // Fallback: direct auth URL 59 window.location.assign('https://auth.haiphen.io/login?to=' + encodeURIComponent(returnTo)); 60 } 61 }); 62 } 63 } 64 65 function renderActivating(mount) { 66 mount.innerHTML = [ 67 '<div class="mkt-intake">', 68 ' <div class="mkt-intake-spinner"></div>', 69 ' <h2 class="mkt-intake-title">Activating subscriptionâ¦</h2>', 70 ' <p class="mkt-intake-desc">Please wait while we link your marketplace account.</p>', 71 '</div>', 72 ].join('\n'); 73 } 74
75 function renderSuccess(mount, provider) { 76 var label = providerLabel(provider); 77 mount.innerHTML = [ 78 '<div class="mkt-intake">', 79 ' <div class="mkt-intake-icon mkt-intake-icon--success">', 80 ' <svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">', 81 ' <circle cx="24" cy="24" r="22" stroke="#10B981" stroke-width="2"/>', 82 ' <path d="M15 24l7 7 11-14" stroke="#10B981" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>', 83 ' </svg>', 84 ' </div>', 85 ' <h2 class="mkt-intake-title">Subscription Activated</h2>', 86 ' <p class="mkt-intake-desc">Your ' + label + ' subscription is now linked to your Haiphen account. Redirecting to your servicesâ¦</p>', 87 '</div>', 88 ].join('\n'); 89 90 setTimeout(function () { 91 if (typeof window.showSection === 'function') { 92 window.showSection('Services'); 93 history.pushState(null, '', '/services'); 94 } else { 95 window.location.assign('/#services'); 96 } 97 }, 2000); 98 } 99 100 function renderError(mount, message) { 101 mount.innerHTML = [ 102 '<div class="mkt-intake">', 103 ' <div class="mkt-intake-icon mkt-intake-icon--error">', 104 ' <svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">', 105 ' <circle cx="24" cy="24" r="22" stroke="#EF4444" stroke-width="2"/>', 106 ' <path d="M16 16l16 16M32 16L16 32" stroke="#EF4444" stroke-width="2.5" stroke-linecap="round"/>', 107 ' </svg>', 108 ' </div>', 109 ' <h2 class="mkt-intake-title">Activation Failed</h2>', 110 ' <p class="mkt-intake-desc">' + (message || 'Something went wrong.') + '</p>', 111 ' <p class="mkt-intake-note">Need help? Contact <a href="mailto:[email protected]">[email protected]</a></p>', 112 '</div>', 113 ].join('\n'); 114 } 115 116 function renderInvalid(mount) { 117 mount.innerHTML = [ 118 '<div class="mkt-intake">', 119 ' <h2 class="mkt-intake-title">Invalid Link</h2>', 120 ' <p class="mkt-intake-desc">This marketplace link is invalid or has expired.</p>', 121 ' <p class="mkt-intake-note">Return to the marketplace listing and click the subscription link again,', 122 ' or contact <a href="mailto:[email protected]">[email protected]</a> for assistance.</p>', 123 '</div>', 124 ].join('\n'); 125 } 126 127 async function activateSubscription(mount, provider, token) { 128 renderActivating(mount); 129 130 var endpoint = RESOLVE_ENDPOINTS[provider]; 131 if (!endpoint) { 132 renderError(mount, 'Unknown marketplace provider: ' + provider); 133 return; 134 } 135 136 try { 137 var resp = await fetch(endpoint, { 138 method: 'POST', 139 headers: { 'Content-Type': 'application/json' }, 140 credentials: 'include', 141 body: JSON.stringify({ token: token }), 142 }); 143 144 var data = null; 145 try { data = await resp.json(); } catch(e) {} 146 147 if (!resp.ok || (data && data.ok === false)) { 148 var errMsg = (data && (data.error || data.message)) || ('HTTP ' + resp.status); 149 renderError(mount, 'Activation error: ' + errMsg); 150 clearStored(); 151 return; 152 } 153 154 clearStored(); 155 renderSuccess(mount, provider); 156 } catch (err) { 157 renderError(mount, 'Network error. Please check your connection and try again.'); 158 } 159 } 160 161 function injectCss() { 162 if (document.querySelector('style[data-haiphen="mkt-intake-css"]')) return; 163 var style = document.createElement('style'); 164 style.setAttribute('data-haiphen', 'mkt-intake-css'); 165 style.textContent = [ 166 '.mkt-intake { max-width: 480px; margin: 80px auto; padding: 40px 32px; text-align: center;', 167 ' background: var(--surface, #1a1f2e); border: 1px solid var(--border, #2d3748);', 168 ' border-radius: 12px; }', 169 '.mkt-intake-icon { margin-bottom: 24px; }', 170 '.mkt-intake-icon--success svg circle { stroke: #10B981; }', 171 '.mkt-intake-icon--error svg circle { stroke: #EF4444; }', 172 '.mkt-intake-title { font-size: 1.5rem; font-weight: 600; margin: 0 0 12px;', 173 ' color: var(--text-primary, #e2e8f0); }', 174 '.mkt-intake-desc { color: var(--text-secondary, #94a3b8); margin: 0 0 24px; line-height: 1.6; }', 175 '.mkt-intake-note { font-size: 0.85rem; color: var(--text-muted, #64748b); margin: 16px 0 0; }', 176 '.mkt-intake-note a { color: var(--accent, #5A9BD4); text-decoration: none; }', 177 '.mkt-intake-note a:hover { text-decoration: underline; }', 178 '.mkt-intake-spinner { width: 40px; height: 40px; border: 3px solid var(--border, #2d3748);', 179 ' border-top-color: #5A9BD4; border-radius: 50%; animation: mkt-spin 0.8s linear infinite;', 180 ' margin: 0 auto 24px; }', 181 '@keyframes mkt-spin { to { transform: rotate(360deg); } }', 182 '.mkt-signin-btn { margin-top: 8px; }', 183 ].join(' '); 184 document.head.appendChild(style); 185 } 186 187 function mount() { 188 var mountEl = document.getElementById('marketplace-intake-mount'); 189 if (!mountEl) return; 190 191 injectCss(); 192 193 var qs = new URLSearchParams(window.location.search); 194 var activate = qs.get('activate'); 195 var provider = getStoredProvider(); 196 var token = getStoredToken(); 197 198 // Capture provider + token from URL query params (fulfillment redirect) 199 var urlProvider = qs.get('provider'); 200 var urlToken = qs.get('token'); 201 if (urlProvider && urlToken) { 202 _mktToken = urlToken; 203 _mktProvider = urlProvider; 204 provider = urlProvider; 205 token = urlToken; 206 } 207 208 if (activate === '1' && provider && token) { 209 // User has just returned from OAuth â activate the subscription 210 activateSubscription(mountEl, provider, token); 211 return; 212 } 213 214 if (token && provider) { 215 // Token present in sessionStorage but no activate flag â show sign-in prompt 216 renderConnecting(mountEl, provider); 217 return; 218 } 219
220 // No token, no activate â link is invalid 221 renderInvalid(mountEl); 222 } 223 224 window.HAIPHEN.MarketplaceIntake = { mount: mount }; 225 226 // Auto-mount when section is revealed (showSection sets innerHTML then calls mount) 227 document.addEventListener('DOMContentLoaded', function () { 228 // If the page was loaded directly on /marketplace, mount immediately 229 if (window.location.pathname === '/marketplace' || 230 new URLSearchParams(window.location.search).get('activate') === '1' || 231 new URLSearchParams(window.location.search).get('token')) { 232 // routeFromUrl will trigger â just ensure mount is called after section renders 233 setTimeout(mount, 50); 234 } 235 }); 236 237})();
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.