UNPKG

onairos

Version:

The Onairos Library is a collection of functions that enable Applications to connect and communicate data with Onairos Identities via User Authorization. Integration for developers is seamless, simple and effective for all applications. LLM SDK capabiliti

411 lines (373 loc) 18.5 kB
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>OAuth Callback - Onairos</title> <style> /* Plain white page for OAuth callback - minimal UI */ body { margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; background: #ffffff; display: flex; align-items: center; justify-content: center; min-height: 100vh; color: #333; } .container { text-align: center; background: #ffffff; padding: 40px 30px; max-width: 400px; color: #333; } .success-icon, .error-icon { font-size: 48px; margin-bottom: 20px; } .message { font-size: 20px; font-weight: 600; margin-bottom: 10px; } .details { font-size: 14px; color: #666; margin-bottom: 20px; } .spinner { border: 3px solid #f3f3f3; border-top: 3px solid #667eea; border-radius: 50%; width: 40px; height: 40px; animation: spin 1s linear infinite; margin: 0 auto 20px; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } /* Hide all content by default for success state */ #success-state .success-icon, #success-state .message, #success-state .details { display: none; } </style> </head> <body> <div class="container"> <!-- Start with blank page for all success cases --> <div id="loading-state" style="display: none;"> <div class="spinner"></div> <div class="message">Processing your connection...</div> <div class="details">Please wait while we complete the setup.</div> </div> <div id="success-state" style="display: none;"> <div class="success-icon"></div> <div class="message">Successfully Connected!</div> <div class="details" id="success-details">You can close this tab now.</div> </div> <div id="error-state" style="display: none;"> <div class="error-icon"></div> <div class="message">Connection Failed</div> <div class="details" id="error-details">Please try again.</div> </div> </div> <script> // Parse URL parameters const urlParams = new URLSearchParams(window.location.search); const success = urlParams.get('success'); const error = urlParams.get('error'); const platform = urlParams.get('platform'); const returnUrlParam = urlParams.get('returnUrl'); const stateParam = urlParams.get('state'); const details = urlParams.get('details'); const email = urlParams.get('email'); // Extract email from URL params // Get elements const loadingState = document.getElementById('loading-state'); const successState = document.getElementById('success-state'); const errorState = document.getElementById('error-state'); const errorDetails = document.getElementById('error-details'); const successDetails = document.getElementById('success-details'); // Detect if we're in a popup or tab const isInPopup = window.opener !== null && !window.opener.closed; const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent); // Try to recover returnUrl from OAuth `state` (crucial for cross-origin flows where localStorage isn't shared) const extractReturnUrlFromState = (rawState) => { if (!rawState) return null; const variants = []; try { variants.push(rawState); } catch (_) {} try { variants.push(decodeURIComponent(rawState)); } catch (_) {} try { variants.push(atob(rawState)); } catch (_) {} try { variants.push(decodeURIComponent(atob(rawState))); } catch (_) {} for (const v of variants) { if (!v) continue; try { const obj = JSON.parse(v); const maybe = obj?.returnUrl || obj?.redirectUrl || obj?.callbackUrl || obj?.return_url || obj?.returnURL; if (typeof maybe === 'string' && maybe.length > 0) return maybe; } catch (_) { // ignore parse failures } } return null; }; const stateReturnUrl = extractReturnUrlFromState(stateParam); if (stateReturnUrl) { try { localStorage.setItem('onairos_last_return_url', stateReturnUrl); } catch (_) {} } const resolveReturnUrl = () => { return ( returnUrlParam || stateReturnUrl || localStorage.getItem('onairos_return_url') || localStorage.getItem('onairos_last_return_url') || '' ); }; console.log('🔄 OAuth callback received:', { success, error, platform, details, email, isInPopup, isMobile, hasReturnUrl: !!resolveReturnUrl() }); if (success === 'true' && platform) { // Success flow console.log(`✅ OAuth success for platform: ${platform}`); // Keep the page plain white - hide all content loadingState.style.display = 'none'; successState.style.display = 'none'; // Signal success to parent window via localStorage (works across tabs) localStorage.setItem(`onairos_${platform}_success`, 'true'); localStorage.setItem(`onairos_${platform}_timestamp`, Date.now().toString()); // Store email if provided (for Gmail OAuth) if (email) { localStorage.setItem(`onairos_${platform}_email`, email); // Also store as generic oauth_email for compatibility localStorage.setItem('onairos_oauth_email', email); localStorage.setItem('onairos_gmail_email', email); // Direct key for Gmail console.log(`📧 Stored ${platform} email in localStorage:`, email); } // Try to close the Capacitor Browser automatically try { if (window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Browser) { console.log('🚪 Attempting to close Capacitor Browser automatically...'); window.Capacitor.Plugins.Browser.close(); console.log('✅ Called Browser.close()'); } } catch (e) { console.log('⚠️ Could not close Capacitor Browser:', e.message); } // Try to notify parent via postMessage (works for popups) try { if (window.opener && !window.opener.closed) { window.opener.postMessage({ type: 'oauth-success', platform: platform, email: email, gmailEmail: email, // Alias for Gmail success: true }, '*'); console.log('📤 Sent onairos oauth-success postMessage to parent window'); } } catch (e) { console.log('⚠️ Could not send postMessage (cross-origin or no opener):', e.message); } // Redirect behavior: // - For popups: close immediately // - For same-page redirects (mobile): redirect back to stored return URL if (isInPopup) { // Popup window - close immediately setTimeout(() => { console.log('🚪 Closing OAuth popup window (success)'); try { window.close(); } catch (e) { console.log('⚠️ Could not close popup:', e.message); } }, 100); } else { // Same-page redirect (mobile) - redirect back to stored return URL const returnUrl = resolveReturnUrl(); // If we have a return URL, ALWAYS go back (mobile Safari often loses opener/context). if (returnUrl) { console.log('🔄 Redirecting back to:', returnUrl); successDetails.textContent = 'Redirecting back...'; // Build redirect URL with success params let redirectUrl; try { redirectUrl = new URL(returnUrl); } catch (e) { redirectUrl = new URL(window.location.origin); } redirectUrl.searchParams.set('onairos_oauth_success', 'true'); redirectUrl.searchParams.set('onairos_oauth_platform', platform); if (email) { redirectUrl.searchParams.set('onairos_oauth_email', email); } // Keep connector context so the SDK can reliably re-open UniversalOnboarding after redirect, // even if query params get stripped or a SPA router eats them. // The SDK (OnairosButton) is responsible for clearing these keys once it consumes them. try { localStorage.setItem('onairos_post_oauth_flow', 'onboarding'); } catch (_) {} // Redirect immediately console.log('📍 Redirecting to:', redirectUrl.toString()); window.location.replace(redirectUrl.toString()); } else { // Fallback: show message with manual navigation option const appUrl = window.location.origin; successDetails.innerHTML = ` <div style="margin-bottom: 16px;">Connection successful! You can close this tab.</div> <a href="${appUrl}" style="display: inline-block; padding: 12px 24px; background: #667eea; color: white; text-decoration: none; border-radius: 8px; font-weight: 600;" onclick="localStorage.setItem('onairos_should_check_oauth', 'true');"> Return to App </a> `; } } } else if (error || success === 'false') { // Error flow console.log(`❌ OAuth error for platform: ${platform}`, error); // Show error state loadingState.style.display = 'none'; errorState.style.display = 'block'; if (errorDetails) { errorDetails.textContent = error || details || 'Please try again.'; } // Signal error to parent window const platformKey = platform || 'unknown'; localStorage.setItem(`onairos_${platformKey}_error`, error || 'Unknown error'); localStorage.setItem(`onairos_${platformKey}_timestamp`, Date.now().toString()); // Try to notify parent via postMessage (works for popups on desktop web) try { if (window.opener && !window.opener.closed) { window.opener.postMessage({ type: 'oauth-failure', platform: platformKey, error: error || 'Unknown error', success: false }, '*'); console.log('📤 Sent onairos oauth-failure postMessage to parent window'); } } catch (e) { console.log('⚠️ Could not send postMessage (cross-origin or no opener):', e.message); } // Desktop popup: close immediately (same behavior as success). // Redirecting here can strand a popup/tab on api2.onairos.uk even though the parent can recover. if (isInPopup) { // Try to close the Capacitor Browser automatically (native flows can surface errors too) try { if (window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Browser) { console.log('🚪 Attempting to close Capacitor Browser automatically (error)...'); window.Capacitor.Plugins.Browser.close(); console.log('✅ Called Browser.close()'); } } catch (e) { console.log('⚠️ Could not close Capacitor Browser:', e.message); } setTimeout(() => { console.log('🚪 Closing OAuth popup window (error)'); try { window.close(); } catch (e) { console.log('⚠️ Could not close popup:', e.message); } }, 100); return; } // Non-popup (mobile/new-tab): redirect back if we can resolve a return URL. // On mobile, the OAuth callback is often a full tab (no window.opener) and cannot be closed by script. let returnUrl = resolveReturnUrl(); // AGGRESSIVE FALLBACK for mobile: if we have no returnUrl but we're on a mobile device, // try to go back to the referring page or the app origin. if (!returnUrl && isMobile) { try { // Try document.referrer first (the page that initiated the OAuth) if (document.referrer && document.referrer.length > 0) { returnUrl = document.referrer; console.log('📱 Mobile error fallback: using document.referrer:', returnUrl); } else { // Last resort: go to the origin root (localhost:3000, etc) returnUrl = window.location.origin; console.log('📱 Mobile error fallback: using window.location.origin:', returnUrl); } } catch (e) { console.log('⚠️ Could not resolve mobile fallback URL:', e.message); } } if (returnUrl) { try { let redirectUrl; try { redirectUrl = new URL(returnUrl); } catch (e) { redirectUrl = new URL(window.location.origin); } redirectUrl.searchParams.set('onairos_oauth_success', 'false'); redirectUrl.searchParams.set('onairos_oauth_platform', platformKey); redirectUrl.searchParams.set('onairos_oauth_error', error || 'Unknown error'); console.log('📍 Redirecting back (error) to:', redirectUrl.toString()); try { localStorage.removeItem('onairos_return_url'); localStorage.removeItem('onairos_oauth_context'); localStorage.removeItem('onairos_oauth_platform'); } catch (_) {} window.location.replace(redirectUrl.toString()); return; } catch (e) { console.log('⚠️ Error redirect back failed:', e.message); } } // No returnUrl in a non-popup context: show error briefly, then try history.back() as last resort console.log('⚠️ No returnUrl available - showing error briefly then attempting history.back()'); setTimeout(() => { console.log('🔙 Attempting history.back() to escape error page'); try { window.history.back(); } catch (e) { console.log('⚠️ history.back() failed:', e.message); } }, 3000); } else { // No clear success or error - treat as potential success console.log('🤔 Ambiguous OAuth callback, treating as success'); // Keep the page plain white - hide all content loadingState.style.display = 'none'; successState.style.display = 'none'; if (platform) { localStorage.setItem(`onairos_${platform}_success`, 'true'); localStorage.setItem(`onairos_${platform}_timestamp`, Date.now().toString()); } // Best-effort close after a short delay setTimeout(() => { console.log('🚪 Attempting to close OAuth window (ambiguous result)'); try { window.close(); } catch (e) { console.log('⚠️ Could not close window:', e.message); } }, 2000); } // Fallback close attempt ONLY for real popups. For mobile/new-tab flows this is noisy and blocked. if (isInPopup) { setTimeout(() => { console.log('🚪 Fallback: Attempting to close OAuth window'); try { window.close(); } catch (e) { console.log('⚠️ Fallback: Could not close window:', e.message); } }, 5000); } // Handle cases where window.close() might not work window.addEventListener('beforeunload', () => { console.log('🚪 OAuth popup window closing'); }); </script> </body> </html>