// Opens sub3's hosted /login and /checkout pages. Copy this file into any web app (React, Vue, Svelte, plain // JS). Importing it does not touch `window`, so it is safe in server-side rendering. // The checkout path for one item of a product: /checkout?product=acme/app&item=pro&units=12&user=0x… // units: months for a tier (12 = a year, billed as 10), packs for a pack; ignored for lifetime items. export function checkoutPath(product, item, { units, user } = {}) { const params = new URLSearchParams({ product, item }); if (units) params.set('units', String(units)); if (user) params.set('user', user); // who receives it; default is the paying wallet return `/checkout?${params}`; } // Opens `path` on the sub3 site and resolves with its result. Call it from a click handler (popup blockers). // With { redirect: true } the page navigates instead and the result comes back in the URL fragment. export function openSub3(path, { site, redirect = false } = {}) { const origin = new URL(site).origin; const url = new URL(path, origin); if (redirect) { url.searchParams.set('return', location.origin + location.pathname + location.search); location.assign(url.href); return new Promise(() => {}); // the page is leaving } return new Promise((resolve, reject) => { const popup = window.open(url.href, 'sub3', 'popup,width=440,height=760'); if (!popup) return reject(new Error('The popup was blocked. Allow popups or use redirects.')); const timer = setInterval(() => popup.closed && stop(new Error('The window was closed')), 500); function stop(error, result) { clearInterval(timer); window.removeEventListener('message', receive); if (error) reject(error); else resolve(result); } function receive(event) { if (event.origin !== origin || event.source !== popup) return; const message = event.data ?? {}; if (message.type === 'sub3:ready') popup.postMessage({ type: 'sub3:hello' }, origin); if (message.type === 'sub3:result') stop(null, message.result); if (message.type === 'sub3:cancel') stop(new Error(message.reason || 'Cancelled')); } window.addEventListener('message', receive); }); } // After a redirect: the result from the URL fragment (#sub3=… or #sub3_error=…), or null. Clears the fragment. export function readSub3Result() { const hash = new URLSearchParams(location.hash.slice(1)); if (!hash.has('sub3') && !hash.has('sub3_error')) return null; history.replaceState(null, '', location.pathname + location.search); if (hash.has('sub3_error')) throw new Error(hash.get('sub3_error') || 'Cancelled'); const base64 = hash.get('sub3').replace(/-/g, '+').replace(/_/g, '/'); const bytes = Uint8Array.from(atob(base64), (c) => c.charCodeAt(0)); return JSON.parse(new TextDecoder().decode(bytes)); }