UNPKG

unified-video-framework

Version:

Cross-platform video player framework supporting iOS, Android, Web, Smart TVs (Samsung/LG), Roku, and more

905 lines 40.7 kB
import { EmailAuthController } from './EmailAuthController.js'; export class PaywallController { constructor(config, opts) { this.config = null; this.overlayEl = null; this.gatewayStepEl = null; this.popup = null; this.emailAuth = null; this.authenticatedUserId = null; this.sessionToken = null; this.currentGateway = null; this.onMessage = async (ev) => { const d = ev?.data || {}; if (!d || d.type !== 'uvfCheckout') return; try { if (this.popup && !this.popup.closed) this.popup.close(); } catch (_) { } this.popup = null; const gateway = this.findGatewayById(d.gatewayId) || this.currentGateway || { id: 'unknown', name: 'Payment Gateway' }; if (d.status === 'success' || d.status === 'cancel' || d.status === 'error') { this.currentGateway = null; } if (d.status === 'cancel') { console.log(`[PaywallController] Payment cancelled for gateway: ${gateway.id}`); if (this.opts.onPaymentCancel) { this.opts.onPaymentCancel(gateway); } this.showGateways(); return; } if (d.status === 'success') { console.log(`[PaywallController] Payment successful for gateway: ${gateway.id}`); try { if (d.sessionId && this.config) { console.log('[PaywallController] Verifying Stripe session'); await fetch(`${this.config.apiBase}/api/rentals/stripe/confirm`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ sessionId: d.sessionId, userId: this.authenticatedUserId || this.config.userId, videoId: this.config.videoId }) }); } if (d.orderId && this.config) { console.log('[PaywallController] Verifying Cashfree order'); await fetch(`${this.config.apiBase}/api/rentals/cashfree/verify?orderId=${encodeURIComponent(d.orderId)}&userId=${encodeURIComponent(this.authenticatedUserId || this.config.userId || '')}&videoId=${encodeURIComponent(this.config.videoId || '')}`); } } catch (error) { console.error('[PaywallController] Payment verification failed:', error); if (this.opts.onPaymentError) { this.opts.onPaymentError(gateway, error); return; } } console.log('[PaywallController] Payment verification completed, proceeding with success flow'); if (this.opts.onPaymentSuccess) { console.log('[PaywallController] Calling onPaymentSuccess callback'); this.opts.onPaymentSuccess(gateway, { sessionId: d.sessionId, orderId: d.orderId, transactionId: d.transactionId, ...d }); } console.log('[PaywallController] Closing overlay and resuming playback with full access'); this.destroyOverlays(); setTimeout(() => { this.opts.onResume({ accessGranted: true, paymentSuccessful: true }); }, 50); return; } if (d.status === 'error') { console.error(`[PaywallController] Payment error for gateway: ${gateway.id}`, d.error); if (this.opts.onPaymentError) { this.opts.onPaymentError(gateway, d.error || 'Payment failed'); } this.showGateways(); } }; this.config = config; this.opts = opts; this.initializeEmailAuth(); try { window.addEventListener('message', this.onMessage, false); } catch (_) { } } updateConfig(config) { const hadWorkingEmailAuth = this.config?.emailAuth?.enabled && !!this.emailAuth; const newConfigLacksEmailAuth = !config?.emailAuth?.enabled; if (hadWorkingEmailAuth && newConfigLacksEmailAuth) { console.log('[PaywallController] Preserving email auth instance during config update'); this.config = { ...config, emailAuth: this.config?.emailAuth }; if (this.emailAuth) { this.emailAuth.updateConfig(this.config); } } else { this.config = config; this.initializeEmailAuth(); if (this.emailAuth) { this.emailAuth.updateConfig(config); } } } openOverlay() { console.log('[PaywallController] openOverlay called'); console.log('[PaywallController] config enabled:', this.config?.enabled); console.log('[PaywallController] email auth enabled:', this.config?.emailAuth?.enabled); console.log('[PaywallController] emailAuth instance:', !!this.emailAuth); if (!this.config?.enabled) { console.log('[PaywallController] Paywall disabled, exiting'); return; } if (this.overlayEl && this.overlayEl.style.display !== 'none') { console.log('[PaywallController] Overlay already visible, skipping'); return; } const hasAccessMenu = this.config.access && (this.config.access.showRegisterBtn || this.config.access.showSubscribeBtn || this.config.access.showRentBtn); if (this.config.emailAuth?.enabled && !hasAccessMenu) { console.log('[PaywallController] Email auth is enabled, checking authentication'); if (!this.emailAuth) { console.log('[PaywallController] Email auth enabled but no instance found, initializing now'); this.initializeEmailAuth(); } if (!this.emailAuth) { console.error('[PaywallController] Failed to initialize email auth, proceeding to payment overlay'); } else { const isAuthenticated = this.emailAuth.isAuthenticated(); console.log('[PaywallController] User authenticated:', isAuthenticated); if (!isAuthenticated) { console.log('[PaywallController] User not authenticated, opening email auth modal'); this.emailAuth.openAuthModal(); return; } else { console.log('[PaywallController] User already authenticated, proceeding to payment overlay'); this.authenticatedUserId = this.emailAuth.getAuthenticatedUserId() || this.config.userId || null; if (this.authenticatedUserId && this.config) { this.config.userId = this.authenticatedUserId; } } } } if (hasAccessMenu && this.config.emailAuth?.enabled && !this.emailAuth) { this.initializeEmailAuth(); } if (this.emailAuth?.isAuthenticated()) { this.authenticatedUserId = this.emailAuth.getAuthenticatedUserId() || this.config.userId || null; if (this.authenticatedUserId && this.config) { this.config.userId = this.authenticatedUserId; } } console.log('[PaywallController] Showing payment overlay'); const root = this.ensureOverlay(); if (!root) { console.log('[PaywallController] Failed to create overlay'); return; } root.style.display = 'flex'; root.classList.add('active'); void root.offsetWidth; root.style.opacity = '1'; const modal = root.querySelector('.uvf-paywall-modal'); if (modal) { modal.style.transform = 'translateY(0)'; modal.style.opacity = '1'; } console.log('[PaywallController] Payment overlay displayed successfully'); this.opts.onShow?.(); } closeOverlay() { console.log('[PaywallController] closeOverlay called'); if (this.emailAuth) { console.log('[PaywallController] Closing auth modal if open'); this.emailAuth.closeAuthModal(); } this.opts.onClose?.(); if (this.overlayEl) { console.log('[PaywallController] Animating paywall overlay out'); this.overlayEl.style.pointerEvents = 'none'; this.overlayEl.style.opacity = '0'; const modal = this.overlayEl.querySelector('.uvf-paywall-modal'); if (modal) { modal.style.transform = 'translateY(20px)'; modal.style.opacity = '0'; } setTimeout(() => { if (this.overlayEl) { this.overlayEl.classList.remove('active'); this.overlayEl.style.display = 'none'; this.overlayEl.style.pointerEvents = ''; console.log('[PaywallController] Paywall overlay hidden after animation'); } }, 300); } const container = this.opts.getOverlayContainer() || document.body; const allOverlays = container.querySelectorAll('.uvf-paywall-overlay, .uvf-auth-overlay'); allOverlays.forEach((overlay) => { const htmlOverlay = overlay; if (htmlOverlay && htmlOverlay.style.display !== 'none') { console.log('[PaywallController] Force hiding leftover overlay:', htmlOverlay.className); htmlOverlay.style.display = 'none'; htmlOverlay.classList.remove('active'); } }); } ensureOverlay() { if (this.overlayEl && document.body.contains(this.overlayEl)) return this.overlayEl; const container = this.opts.getOverlayContainer() || document.body; const ov = document.createElement('div'); ov.className = 'uvf-paywall-overlay'; ov.setAttribute('role', 'dialog'); ov.setAttribute('aria-modal', 'true'); ov.style.cssText = ` position: absolute; inset: 0; background: rgba(0, 0, 0, 0.95); z-index: 2147483647; display: none; align-items: center; justify-content: center; opacity: 0; transition: opacity 0.3s ease; `; const modal = document.createElement('div'); modal.className = 'uvf-paywall-modal'; modal.style.cssText = ` width: 90vw; height: 85vh; max-width: 1000px; max-height: 700px; background: #0f0f10; border: 1px solid rgba(255, 255, 255, 0.2); border-radius: 16px; display: flex; flex-direction: column; overflow: hidden; box-shadow: 0 20px 60px rgba(0, 0, 0, 0.7), 0 0 0 1px rgba(255, 255, 255, 0.1); transform: translateY(20px); opacity: 0; transition: transform 0.3s ease, opacity 0.3s ease; `; const header = document.createElement('div'); header.style.cssText = 'display:flex;gap:16px;align-items:center;padding:16px 20px;border-bottom:1px solid rgba(255,255,255,0.1)'; const hTitle = document.createElement('div'); hTitle.textContent = (this.config?.branding?.title || 'Continue watching'); hTitle.style.cssText = 'color:#fff;font-size:18px;font-weight:700'; const hDesc = document.createElement('div'); hDesc.textContent = (this.config?.branding?.description || 'Rent to continue watching this video.'); hDesc.style.cssText = 'color:rgba(255,255,255,0.75);font-size:14px;margin-top:4px'; const headerTextWrap = document.createElement('div'); headerTextWrap.appendChild(hTitle); headerTextWrap.appendChild(hDesc); header.appendChild(headerTextWrap); const content = document.createElement('div'); content.style.cssText = 'flex:1;display:flex;align-items:center;justify-content:center;padding:20px;'; const intro = document.createElement('div'); intro.style.cssText = 'display:flex;flex-direction:column;gap:16px;align-items:center;justify-content:center;'; const accessCfg = this.config?.access; const msg = document.createElement('div'); msg.textContent = accessCfg?.statusMessage || 'Free preview ended. Rent to continue watching.'; msg.style.cssText = 'color:#fff;font-size:16px;'; intro.appendChild(msg); const btnStyle = 'color:#fff;border:none;border-radius:999px;padding:10px 18px;cursor:pointer;font-size:14px;font-weight:600;min-width:180px;'; if (accessCfg && (accessCfg.showRegisterBtn || accessCfg.showSubscribeBtn || accessCfg.showRentBtn)) { if (accessCfg.showRegisterBtn) { const loginBtn = document.createElement('button'); loginBtn.textContent = 'Login / Signup'; loginBtn.className = 'uvf-btn-login'; loginBtn.style.cssText = btnStyle + 'background:linear-gradient(135deg,#1890ff,#096dd9);border:1px solid rgba(24,144,255,0.6);'; loginBtn.addEventListener('click', () => { if (accessCfg.onLoginClick) { accessCfg.onLoginClick(); } else { if (!this.emailAuth) this.initializeEmailAuth(); this.emailAuth?.openAuthModal(); } }); intro.appendChild(loginBtn); } if (accessCfg.showSubscribeBtn) { const subBtn = document.createElement('button'); subBtn.textContent = 'Become a Subscriber'; subBtn.className = 'uvf-btn-subscribe'; subBtn.style.cssText = btnStyle + 'background:linear-gradient(135deg,#52c41a,#389e0d);border:1px solid rgba(82,196,26,0.6);'; subBtn.addEventListener('click', () => { if (accessCfg.subscribeUrl) { window.open(accessCfg.subscribeUrl, '_blank'); } }); intro.appendChild(subBtn); } if (accessCfg.showRentBtn) { const rentBtn = document.createElement('button'); rentBtn.textContent = 'Rent Now'; rentBtn.className = 'uvf-btn-primary'; rentBtn.style.cssText = btnStyle + 'background:linear-gradient(135deg,#ff4d4f,#d9363e);border:1px solid rgba(255,77,79,0.6);'; rentBtn.addEventListener('click', () => this.openRentDestination()); intro.appendChild(rentBtn); } } else { const rentBtn = document.createElement('button'); rentBtn.textContent = 'Rent Now'; rentBtn.className = 'uvf-btn-primary'; rentBtn.style.cssText = btnStyle + 'background:linear-gradient(135deg,#ff4d4f,#d9363e);border:1px solid rgba(255,77,79,0.6);'; rentBtn.addEventListener('click', () => this.openRentDestination()); intro.appendChild(rentBtn); } const step = document.createElement('div'); step.style.cssText = 'display:none;flex-direction:column;gap:16px;align-items:center;justify-content:center;'; this.gatewayStepEl = step; content.appendChild(intro); content.appendChild(step); modal.appendChild(header); modal.appendChild(content); ov.appendChild(modal); container.appendChild(ov); this.overlayEl = ov; return ov; } destroyOverlays() { console.log('[PaywallController] destroyOverlays called'); if (this.emailAuth) { this.emailAuth.closeAuthModal(); } if (this.overlayEl && this.overlayEl.parentNode) { this.overlayEl.parentNode.removeChild(this.overlayEl); this.overlayEl = null; } const container = this.opts.getOverlayContainer() || document.body; const allOverlays = container.querySelectorAll('.uvf-paywall-overlay, .uvf-auth-overlay'); allOverlays.forEach((overlay) => { if (overlay.parentNode) { console.log('[PaywallController] Destroying leftover overlay:', overlay.className); overlay.parentNode.removeChild(overlay); } }); } hideLoginButton() { if (this.overlayEl) { const loginBtn = this.overlayEl.querySelector('.uvf-btn-login'); if (loginBtn) loginBtn.style.display = 'none'; } } openRentDestination() { const accessCfg = this.config?.access; const rentUrl = (accessCfg?.rentUrl || '').toString().trim(); if (rentUrl) { try { window.open(rentUrl, '_blank'); return; } catch (error) { console.error('[PaywallController] Failed to open access.rentUrl, falling back to payment gateways:', error); } } this.showGateways(); } showGateways() { if (!this.config) { console.error('[PaywallController] No config found in showGateways'); return; } console.log('[PaywallController] showGateways called'); console.log('[PaywallController] Config gateways:', this.config.gateways); this.gatewayStepEl.innerHTML = ''; this.gatewayStepEl.style.display = 'flex'; const title = document.createElement('div'); title.textContent = this.config.branding?.paymentTitle || 'Choose a payment method'; title.style.cssText = 'color:#fff;font-size:16px;margin-bottom:20px;'; const wrap = document.createElement('div'); wrap.style.cssText = 'display:flex;gap:12px;flex-wrap:wrap;justify-content:center;'; const gateways = this.getGateways(); console.log('[PaywallController] Processed gateways:', gateways); if (gateways.length === 0) { console.warn('[PaywallController] No gateways available'); const errorMsg = document.createElement('div'); errorMsg.textContent = 'No payment methods available. Please contact support.'; errorMsg.style.cssText = 'color:#ff6b6b;font-size:14px;text-align:center;padding:20px;'; wrap.appendChild(errorMsg); } else { let buttonsAdded = 0; for (const gateway of gateways) { console.log(`[PaywallController] Creating button for gateway:`, gateway); const btn = this.createGatewayButton(gateway); btn.addEventListener('click', () => this.handleGatewayClick(gateway)); wrap.appendChild(btn); buttonsAdded++; } console.log(`[PaywallController] Added ${buttonsAdded} gateway buttons`); } const intro = this.overlayEl?.querySelector('div[style*="display:flex;flex-direction:column;gap:16px;align-items:center;justify-content:center;"]'); if (intro) { intro.style.display = 'none'; } this.gatewayStepEl.appendChild(title); this.gatewayStepEl.appendChild(wrap); console.log('[PaywallController] Gateway step UI updated'); } getGateways() { if (!this.config?.gateways) return []; return this.config.gateways.map((g) => { if (typeof g === 'string') { return this.getLegacyGateway(g); } return g; }); } getLegacyGateway(id) { const legacyGateways = { stripe: { id: 'stripe', name: 'Credit/Debit Card', description: 'Pay with Stripe', color: '#6772e5' }, cashfree: { id: 'cashfree', name: 'UPI/Netbanking', description: 'Pay with Cashfree', color: '#00d4aa' }, payu: { id: 'payu', name: 'PayU', description: 'Pay with PayU', color: '#17bf43' }, custom: { id: 'custom', name: 'Pay Now', description: 'Secure Payment', color: '#4f9eff' } }; console.log(`[PaywallController] Converting legacy gateway: ${id}`); const gateway = legacyGateways[id] || { id, name: id.charAt(0).toUpperCase() + id.slice(1), description: `Pay with ${id}`, color: '#666666' }; console.log(`[PaywallController] Converted to:`, gateway); return gateway; } createGatewayButton(gateway) { const btn = document.createElement('button'); btn.className = 'uvf-gateway-btn'; const content = document.createElement('div'); content.style.cssText = 'display:flex;flex-direction:column;align-items:center;gap:8px;'; if (gateway.icon) { const icon = document.createElement('div'); icon.innerHTML = gateway.icon; icon.style.cssText = 'font-size:24px;'; content.appendChild(icon); } const name = document.createElement('div'); name.textContent = gateway.name; name.style.cssText = 'font-weight:600;font-size:14px;'; content.appendChild(name); if (gateway.description) { const desc = document.createElement('div'); desc.textContent = gateway.description; desc.style.cssText = 'font-size:12px;opacity:0.8;'; content.appendChild(desc); } btn.appendChild(content); const bgColor = gateway.color || '#4f9eff'; btn.style.cssText = ` background: linear-gradient(135deg, ${bgColor}, ${this.adjustBrightness(bgColor, -20)}); color: #fff; border: none; border-radius: 12px; padding: 16px 20px; cursor: pointer; min-width: 140px; transition: transform 0.2s ease, box-shadow 0.2s ease; font-family: inherit; `; btn.addEventListener('mouseenter', () => { btn.style.transform = 'translateY(-2px)'; btn.style.boxShadow = `0 8px 20px rgba(0,0,0,0.3), 0 4px 8px ${bgColor}40`; }); btn.addEventListener('mouseleave', () => { btn.style.transform = 'translateY(0)'; btn.style.boxShadow = 'none'; }); return btn; } adjustBrightness(color, amount) { if (!color.startsWith('#')) return color; const num = parseInt(color.slice(1), 16); const r = Math.max(0, Math.min(255, (num >> 16) + amount)); const g = Math.max(0, Math.min(255, ((num >> 8) & 0x00FF) + amount)); const b = Math.max(0, Math.min(255, (num & 0x0000FF) + amount)); return `#${((r << 16) | (g << 8) | b).toString(16).padStart(6, '0')}`; } async openGateway(gateway) { try { if (!this.config) return; const { apiBase, userId, videoId } = this.config; const w = Math.min(window.screen.width - 100, this.config.popup?.width || 1000); const h = Math.min(window.screen.height - 100, this.config.popup?.height || 800); const left = Math.max(0, Math.round((window.screen.width - w) / 2)); const top = Math.max(0, Math.round((window.screen.height - h) / 2)); if (gateway === 'stripe') { const res = await fetch(`${apiBase}/api/rentals/stripe/checkout-session`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ userId, videoId, successUrl: window.location.origin + window.location.pathname + '?rental=success&popup=1', cancelUrl: window.location.origin + window.location.pathname + '?rental=cancel&popup=1' }) }); const data = await res.json(); if (data?.url) { try { this.popup && !this.popup.closed && this.popup.close(); } catch (_) { } this.popup = window.open(data.url, 'uvfCheckout', `popup=1,width=${w},height=${h},left=${left},top=${top}`); this.startPolling(); } return; } if (gateway === 'cashfree') { const features = `popup=1,width=${w},height=${h},left=${left},top=${top}`; let pre = null; try { pre = window.open('', 'uvfCheckout', features); } catch (_) { pre = null; } const res = await fetch(`${apiBase}/api/rentals/cashfree/order`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ userId, videoId, returnUrl: window.location.origin + window.location.pathname }) }); const data = await res.json(); if (data?.paymentLink && data?.orderId) { try { this.popup && !this.popup.closed && this.popup.close(); } catch (_) { } this.popup = pre && !pre.closed ? pre : window.open('', 'uvfCheckout', features); try { if (this.popup) this.popup.location.href = data.paymentLink; } catch (_) { } window._uvf_cfOrderId = data.orderId; this.startPolling(); } else { try { pre && !pre.closed && pre.close(); } catch (_) { } } return; } } catch (_) { } } startPolling() { const timer = setInterval(async () => { if (!this.config) { clearInterval(timer); return; } if (this.popup && this.popup.closed) { clearInterval(timer); this.showGateways(); return; } }, 3000); } async handleGatewayClick(gateway) { try { this.currentGateway = gateway; console.log(`[PaywallController] Processing payment for gateway: ${gateway.id}`); if (this.opts.onPaymentRequested) { console.log(`[PaywallController] Using custom handler for gateway: ${gateway.id}`); const paymentData = { userId: this.authenticatedUserId || this.config?.userId, videoId: this.config?.videoId, amount: this.config?.pricing?.amount, currency: this.config?.pricing?.currency || 'INR', gateway: gateway.id, sessionToken: this.sessionToken }; await this.opts.onPaymentRequested(gateway, paymentData); return; } const paymentLinkConfig = this.config?.paymentLink; if (paymentLinkConfig?.endpoint) { console.log(`[PaywallController] Using payment link configuration for: ${gateway.id}`); await this.handlePaymentLink(gateway); return; } if (gateway.id === 'stripe' || gateway.id === 'cashfree') { console.log(`[PaywallController] Using built-in handler for: ${gateway.id}`); await this.openGateway(gateway.id); return; } console.error(`[PaywallController] No payment handler configured for gateway: ${gateway.id}`); alert('Payment method not configured. Please contact support.'); } catch (error) { console.error(`[PaywallController] Payment error for ${gateway.id}:`, error); if (this.opts.onPaymentError) { this.opts.onPaymentError(gateway, error); } else { alert('Payment failed. Please try again or contact support.'); } this.showGateways(); } } async handlePaymentLink(gateway) { const cfg = this.config.paymentLink; if (!cfg?.endpoint) throw new Error('paymentLink.endpoint is required'); const w = Math.min(window.screen.width - 100, cfg.popup?.width || this.config?.popup?.width || 1000); const h = Math.min(window.screen.height - 100, cfg.popup?.height || this.config?.popup?.height || 800); const left = Math.max(0, Math.round((window.screen.width - w) / 2)); const top = Math.max(0, Math.round((window.screen.height - h) / 2)); const features = cfg.popup?.features || `popup=1,width=${w},height=${h},left=${left},top=${top}`; let pre = null; try { pre = window.open('', 'uvfCheckout', features); } catch (_) { pre = null; } const paymentData = { userId: this.authenticatedUserId || this.config?.userId, videoId: this.config?.videoId, amount: this.config?.pricing?.amount, currency: this.config?.pricing?.currency || 'INR', metadata: { gateway: gateway.id, sessionToken: this.sessionToken, authenticatedUserId: this.authenticatedUserId } }; if (!cfg.mapRequest) { throw new Error('paymentLink.mapRequest is required - please provide a function to map payment data to your API format'); } console.log('[PaywallController] PaymentData passed to mapRequest:', paymentData); const body = cfg.mapRequest(paymentData); console.log('[PaywallController] Mapped request body:', body); const res = await fetch(cfg.endpoint, { method: cfg.method || 'POST', headers: { 'Content-Type': 'application/json', ...(cfg.headers || {}) }, body: (cfg.method || 'POST') === 'POST' ? JSON.stringify(body) : undefined }); console.log('[PaywallController] API response status:', res.status, res.statusText); const raw = await res.json(); const mapped = cfg.mapResponse ? cfg.mapResponse(raw) : { url: raw?.Payment_Link_URL || raw?.paymentLink || raw?.link_url, orderId: raw?.order_id || raw?.orderId }; if (!mapped?.url) { try { pre && !pre.closed && pre.close(); } catch (_) { } throw new Error(raw?.message || 'Failed to create payment link'); } try { this.popup && !this.popup.closed && this.popup.close(); } catch (_) { } this.popup = pre && !pre.closed ? pre : window.open('', 'uvfCheckout', features); try { if (this.popup) this.popup.location.href = mapped.url; } catch (_) { } window._uvf_orderId = mapped.orderId || null; window._uvf_gatewayId = gateway.id; this.startPolling(); } findGatewayById(gatewayId) { if (!gatewayId) return null; const gateways = this.getGateways(); return gateways.find(g => g.id === gatewayId) || null; } initializeEmailAuth() { console.log('[PaywallController] initializeEmailAuth called'); console.log('[PaywallController] email auth config:', this.config?.emailAuth); console.log('[PaywallController] config enabled:', this.config?.enabled); if (!this.config?.enabled) { console.log('[PaywallController] Paywall completely disabled, cleaning up email auth'); if (this.emailAuth) { this.emailAuth.destroy(); this.emailAuth = null; } return; } if (!this.config?.emailAuth?.enabled) { console.log('[PaywallController] Email auth disabled, cleaning up existing instance'); if (this.emailAuth) { this.emailAuth.destroy(); this.emailAuth = null; } return; } console.log('[PaywallController] Email auth enabled, checking for existing instance:', !!this.emailAuth); if (!this.emailAuth) { console.log('[PaywallController] Creating new EmailAuthController'); const emailAuthOptions = { getOverlayContainer: this.opts.getOverlayContainer, onAuthSuccess: (userId, sessionToken, accessData) => { this.authenticatedUserId = userId; this.sessionToken = sessionToken; if (this.config) { this.config.userId = userId; } this.emailAuth?.closeAuthModal(); if (accessData) { const access_granted = accessData.accessGranted ?? accessData.access_granted ?? false; const requires_payment = accessData.requiresPayment ?? accessData.requires_payment ?? false; const free_duration = accessData.freeDuration ?? accessData.free_duration ?? 0; const price = accessData.price ?? null; if (price && this.config) { this.config.pricing = { ...this.config.pricing, amount: parseFloat(price.toString().replace(/[^\d.]/g, '')) }; } console.log('[PaywallController] Auth response:', { access_granted, requires_payment, free_duration }); if (access_granted) { console.log('[PaywallController] Access granted, cleaning up overlays and playing video'); this.destroyOverlays(); setTimeout(() => { if (this.opts.onResume) { this.opts.onResume({ accessGranted: true, paymentSuccessful: true }); } }, 50); } else if (!access_granted && requires_payment) { if (free_duration > 0) { console.log(`[PaywallController] Starting ${free_duration}s free preview`); this.destroyOverlays(); setTimeout(() => { if (this.opts.onResume) { this.opts.onResume({ accessGranted: false, paymentSuccessful: false, freeDuration: free_duration }); } }, 50); } else { console.log('[PaywallController] No preview available, showing paywall'); setTimeout(() => { this.openPaymentOverlay(); this.hideLoginButton(); }, 100); } } else { console.log('[PaywallController] Default behavior, resuming playback'); this.destroyOverlays(); setTimeout(() => { if (this.opts.onResume) { this.opts.onResume({ accessGranted: true, paymentSuccessful: false }); } }, 50); } } else { console.log('[PaywallController] No access data, resuming with default preview'); this.opts.onResume(); } }, onAuthCancel: () => { this.emailAuth?.closeAuthModal(); this.opts.onShow?.(); }, onShow: this.opts.onShow, onClose: this.opts.onClose, }; this.emailAuth = new EmailAuthController(this.config, emailAuthOptions); console.log('[PaywallController] EmailAuthController created successfully'); } } openPaymentOverlay() { console.log('[PaywallController] Opening payment overlay'); const root = this.ensureOverlay(); if (!root) { console.error('[PaywallController] Failed to create overlay'); return; } try { root.style.display = 'flex'; root.classList.add('active'); void root.offsetWidth; root.style.opacity = '1'; const modal = root.querySelector('.uvf-paywall-modal'); if (modal) { modal.style.transform = 'translateY(0)'; modal.style.opacity = '1'; } this.opts.onShow?.(); console.log('[PaywallController] Payment overlay shown'); } catch (err) { console.error('[PaywallController] Error showing overlay:', err); } } isAuthenticated() { if (!this.config?.emailAuth?.enabled) return true; return this.emailAuth?.isAuthenticated() || false; } getAuthenticatedUserId() { if (!this.config?.emailAuth?.enabled) return this.config?.userId || null; return this.emailAuth?.getAuthenticatedUserId() || this.config?.userId || null; } async logout() { if (this.emailAuth) { await this.emailAuth.logout(); } this.authenticatedUserId = null; this.sessionToken = null; } addGateway(gateway) { if (!this.config) { console.warn('[PaywallController] Cannot add gateway: config is null'); return; } if (!this.config.gateways) { this.config.gateways = []; } this.config.gateways = this.config.gateways.filter((g) => { const id = typeof g === 'string' ? g : g.id; return id !== gateway.id; }); this.config.gateways.push(gateway); console.log(`[PaywallController] Added gateway: ${gateway.id}`); } removeGateway(gatewayId) { if (!this.config?.gateways) return; this.config.gateways = this.config.gateways.filter((g) => { const id = typeof g === 'string' ? g : g.id; return id !== gatewayId; }); console.log(`[PaywallController] Removed gateway: ${gatewayId}`); } getConfiguredGateways() { return this.getGateways(); } destroy() { if (this.emailAuth) { this.emailAuth.destroy(); this.emailAuth = null; } if (this.overlayEl && this.overlayEl.parentElement) { this.overlayEl.parentElement.removeChild(this.overlayEl); } this.overlayEl = null; try { if (this.popup && !this.popup.closed) { this.popup.close(); } } catch (_) { } this.popup = null; this.currentGateway = null; try { window.removeEventListener('message', this.onMessage, false); } catch (_) { } } } //# sourceMappingURL=PaywallController.js.map