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 designed to be seamless, simple and effective for all applications

1,019 lines (894 loc) 38.3 kB
import React, {useEffect, useState, useRef} from 'react'; // import {connect, decrypt} from '@othent/kms'; import { Othent, AppInfo } from "@othent/kms"; // import sha256 from 'crypto-js/sha256'; import { rsaEncrypt } from './RSA.jsx'; import getPin from './getPin.js'; // import { Buffer } from 'buffer'; import Overlay from './overlay/overlay.js'; // import { miniApp } from '@telegram-apps/sdk'; import { useLaunchParams } from "@telegram-apps/sdk-react"; // Dynamic import for crypto-js's sha256 const loadSha256 = async () => { const module = await import(/* webpackChunkName: "sha256" */ 'crypto-js/sha256'); return module.default; }; // // Dynamic import for @othent/kms // const loadOthentKms = async () => { // try{ // console.log("Entering Dynamic Othent Load") // const module = await import(/* webpackChunkName: "othent-kms" */ '@othent/kms'); // console.log("DYNAMICALLY LOADED OTHENT") // return module; // }catch(e){ // console.error("Error loading Othent DYnamically : ", e) // } // }; // import Buffer export function OnairosButton({ requestData, webpageName, inferenceData = null, onComplete = null, autoFetch = true, proofMode = false, textLayout = 'below', textColor = 'white', login = false, buttonType = 'pill', loginReturn = null, loginType = 'signIn', visualType = 'full', }) { const isTelegramMiniApp = () => { const userAgent = navigator.userAgent || navigator.vendor || window.opera; return typeof window.Telegram !== 'undefined' && /telegram/i.test(userAgent) && /mobile/i.test(navigator.userAgent); }; const [launchParams, setLaunchParams] = useState(null); // Modified useEffect for launch params useEffect(() => { let mounted = true; if (isTelegramMiniApp()) { const params = useLaunchParams(); if (mounted) { setLaunchParams(params); } } return () => { mounted = false; }; }, []); // Modified useEffect for handling launch parameters useEffect(() => { if (!launchParams) return; console.log('Launch Params:', launchParams); if (launchParams.startParam) { try { setMessage(`Received parameters: ${launchParams.startParam}`); setAuthDialog({ show: true, type: 'callback', data: { startParam: launchParams.startParam } }); } catch (err) { console.error('Error parsing launch params:', err); } } }, [launchParams]); // Modified Telegram initialization useEffect(() => { if (!isTelegramMiniApp()) return; try { const webApp = window.Telegram?.WebApp; if (webApp) { webApp.ready(); } } catch (err) { console.error('Error initializing Telegram WebApp:', err); } }, []); const [userData, setUserData] = useState(null); const [showOverlay, setShowOverlay] = useState(false); const [activeModels, setActiveModels] = useState([]); const [granted, setGranted] = useState(0); const [selectedRequests, setSelectedRequests] = useState({}); const [avatar, setAvatar] = useState(false); const [traits, setTraits] = useState(false); const [othentUser, setOthentUser] = useState(false); const [othentConnected, setOthentConnected] = useState(false); const NoAccount = useRef(false); const NoModel = useRef(false); const [isAuthenticated, setIsAuthenticated] = useState(false); const [authToken, setAuthToken] = useState(null); const [loading, setLoading] = useState(true); const [hashedOthentSub, setHashedOthentSub] = useState(null); const [encryptedPin, setEncryptedPin] = useState(null); const [authDialog, setAuthDialog] = useState({ show: false, type: null, data: null }); const [accountInfo, setAccountInfo] = useState(null); const [isLoading, setIsLoading] = useState(false); const [isProcessingAuth, setIsProcessingAuth] = useState(false); const hasProcessedCallback = useRef(false); const [authError, setAuthError] = useState(null); const [notif, setNotif] = useState({ show:false, color:null, message:null }); const [startParam, setStartParam] = useState(null); const [message, setMessage] = useState("Initializing..."); // const telegram = useTelegram(); // const { webApp } = telegram; const API_URL = 'https://api2.onairos.uk'; // const API_URL = 'http://localhost:8080'; // Modified callback handling useEffect(() => { if (!isTelegramMiniApp()) return; const handleCallback = async () => { const callbackURL = new URL(window.location.href); const code = callbackURL.searchParams.get("code"); const state = callbackURL.searchParams.get("state"); if (code && state && !hasProcessedCallback.current && !isProcessingAuth) { hasProcessedCallback.current = true; setIsProcessingAuth(true); try { // Make API call to Onairos server to get Othent token const response = await fetch(`${API_URL}/auth/callback`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ code, state }) }); if (!response.ok) { throw new Error('Failed to get Othent token'); } const { sessionId } = await response.json(); // Start polling for the Othent token const pollForToken = async () => { const pollInterval = setInterval(async () => { try { const tokenResponse = await fetch(`${API_URL}/auth/token/${sessionId}`, { method: 'GET', headers: { 'Content-Type': 'application/json' } }); if (!tokenResponse.ok) { throw new Error(`HTTP error! Status: ${tokenResponse.status}`); } const data = await tokenResponse.json(); if (data.othentToken) { clearInterval(pollInterval); // Complete authentication with the received token await completeAuth(data.othentToken); setOthentConnected(true); window.history.replaceState({}, document.title, window.location.pathname); } } catch (error) { console.error("Error polling for token:", error); clearInterval(pollInterval); setAuthError("Failed to retrieve authentication token"); setIsProcessingAuth(false); } }, 3000); // Poll every 3 seconds // Clear polling after 2 minutes (timeout) setTimeout(() => { clearInterval(pollInterval); if (!othentConnected) { setAuthError("Authentication timeout"); setIsProcessingAuth(false); } }, 120000); }; pollForToken(); } catch (error) { console.error("Auth callback processing failed:", error); setAuthError(error.message); setIsProcessingAuth(false); } } }; handleCallback(); }, []); const completeAuth = async (othentToken) => { try { const appInfo = { name: "Onairos", version: "1.0.0", env: "production", }; const othent = new Othent({ appInfo, throwErrors: true, auth0LogInMethod: "redirect", auth0RedirectURI: window.location.href, auth0ReturnToURI: window.location.href, }); // Use the provided Othent token to complete authentication const userDetails = await othent.completeConnectionWithToken(othentToken); setIsAuthenticated(true); const sha256 = await loadSha256(); const hashedOthentSub = sha256(userDetails.sub).toString(); setHashedOthentSub(hashedOthentSub); const userOnairosPin = await getPin(hashedOthentSub); setEncryptedPin(userOnairosPin.result); setAuthToken(userOnairosPin.token); await fetchAccountInfo(userDetails.email, true); setShowOverlay(true); localStorage.setItem('othentToken', JSON.stringify(userDetails)); localStorage.setItem('onairosToken', userDetails.token); } catch (error) { setNotif({ show: true, color: 'red', message: 'An error has occurred, please try again', }); console.error("Authentication failed:", error); throw error; } finally { setIsProcessingAuth(false); } }; // Add error display useEffect(() => { if (authError) { // Show error to user (implement your error UI here) console.error("Authentication error:", authError); } }, [authError]); const isMobileDevice = () => { const userAgent = navigator.userAgent || navigator.vendor || window.opera; return /android|iphone|ipad|ipod|windows phone/i.test(userAgent); }; const findLargestDataObject = (arrayOfObjects) => { // Update the hierarchy const hierarchy = { 'Small': 16, 'Medium': 32, 'Large': 64 }; let largestObject = null; let largestValue = 0; arrayOfObjects.forEach(obj => { const currentValue = hierarchy[obj.data]; if (currentValue > largestValue) { largestValue = currentValue; largestObject = obj; } }); return largestValue; }; useEffect(() => { // Only proceed if autoFetch is true and onComplete is a function if (autoFetch && inferenceData && typeof onComplete === 'function') { const handleAPIResponse = async (event) => { if (event.data && event.data.source === 'content-script' && event.data.type === 'API_URL_RESPONSE' && event.data.unique === "Onairos-Response") { const { APIurl, approved, accessToken } = event.data; const trimSize = findLargestDataObject(approved); // Trim the data array based on the allowed number of items const trimmedData = inferenceData.slice(0, trimSize); // Fetch the new anime data from the API URL const jsonData = { Input: trimmedData // Your request payload }; try { const response = await fetch(APIurl, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${accessToken}` }, body: JSON.stringify(jsonData) }); const data = await response.json(); onComplete(data); } catch (error) { console.error(error); onComplete(null, error); } } }; window.addEventListener('message', handleAPIResponse); return () => { window.removeEventListener('message', handleAPIResponse); }; } }, []); const generateRandomData = (structure) => { const generateRandomNumbers = (obj) => { if (Array.isArray(obj)) { return obj.map((item) => generateRandomNumbers(item)); } else if (obj !== null && typeof obj === 'object') { return Object.keys(obj).reduce((acc, key) => { acc[key] = generateRandomNumbers(obj[key]); return acc; }, {}); } else { return Math.random(); } }; return generateRandomNumbers(structure); }; const handleConnectionSelection = (dataRequester, key, index, type, reward, isSelected) => { setSelectedRequests(prev => ({ ...prev, [`${dataRequester}-${key}-${index}`]: { type, reward, isSelected } })); }; const changeGranted = (value) => { setGranted((prev) => Math.max(prev + value, 0)); }; const handleAPIRequestForMobile = async () => { if (isMobileDevice()) { setShowOverlay(true); } return ; }; const rejectDataRequest = () => { setShowOverlay(false); if (onComplete) { onComplete('rejected'); } }; const makeApiCall = async (approvedRequests, pin, othentSub) => { const jsonData = { Info: { EncryptedUserPin: pin, confirmations: approvedRequests, web3Type: 'othent', Domain: window.location.href, proofMode: false, OthentSub: othentSub, }, }; try { const response = await fetch('https://api2.onairos.uk/getAPIurl', { // const response = await fetch('http://localhost:8080/getAPIurl', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(jsonData), }); const data = await response.json(); if (autoFetch && onComplete) { onComplete(data); } else { setAuthDialog({ show: true, type: 'apiURL', data: { code: data, // Updated from response.body.apiUrl to data.apiUrl state: pin, }, }); // chrome.runtime.sendMessage({ // source: 'dataRequestPage', // type: 'returnedAPIurl', // APIurl:response.body.apiUrl, // accessToken:response.body.token, // approved:selectedConnections.current, // }) } } catch (error) { console.error(error); if (onComplete) { onComplete(null, error); } } }; const sendDataRequest = async () => { if (granted <= 0) return; try { // Retrieve approved requests const approvedRequests = Object.values(selectedRequests) .filter((req) => req.isSelected) .map((req) => ({ type: req.type, reward: req.reward })); if (encryptedPin==null && othentUser && !othentConnected) { setAuthDialog({ show: true, type: 'debug', data: { code: 'Connecting Othent Details', state1: encryptedPin, state2: othentConnected, state3: othentUser }, }); const appInfo = { name: 'Onairos', version: '1.0.0', env: 'production', }; const othent = new Othent({ appInfo, throwErrors: false }); const userDetails = await othent.connect(); const sha256 = await loadSha256(); const hashedSub = sha256(userDetails.sub).toString(); setHashedOthentSub(hashedSub); // Wait for the pin to be retrieved // const userOnairosDetails = await getPin((userDetails.sub).toString()); const userOnairosDetails = await getPin(hashedSub); const pin = userOnairosDetails.result; setEncryptedPin(pin); // setAuthToken(userOnairosDetails.token); setOthentConnected(true); setAuthDialog({ show: true, type: 'callback', data: { code: 'Just Before API', state: userOnairosDetails.token, }, }); // Make API call with newly retrieved data await makeApiCall(approvedRequests, pin, hashedSub); } else { // Make API call with existing state if (encryptedPin && hashedOthentSub) { await makeApiCall(approvedRequests, encryptedPin, hashedOthentSub); } else { console.error('Missing required authentication data'); } } } catch (error) { console.error('Error in sendDataRequest:', error); } finally { setShowOverlay(false); } }; const validateRequestData = () => { const validKeys = ['Small', 'Medium', 'Large']; const requiredProperties = ['type', 'descriptions', 'reward']; if( typeof webpageName !== 'string'){ throw new Error(`Property webpageName must be a String`); } for (const key of validKeys) { if (!(key in requestData)) { throw new Error(`Missing key '${key}' in requestData.`); } for (const prop of requiredProperties) { if (!(prop in requestData[key])) { throw new Error(`Missing property '${prop}' in requestData.${key}.`); } if (prop !== 'reward' && typeof requestData[key][prop] !== 'string') { throw new Error(`Property '${prop}' in requestData.${key} must be a string.`); } if (prop !== 'reward' && requestData[key][prop].trim() === '') { throw new Error(`Property '${prop}' in requestData.${key} cannot be empty.`); } } } // Add any other validation rules as necessary }; const checkOnairosExtension = () => { if (typeof window.onairos !== 'undefined') { console.log('Onairos info:', window.onairos.getInfo()); } else { console.log('Onairos is not installed.'); } // Or listen for the onairosReady event window.addEventListener('onairosReady', function() { console.log('Onairos was just detected!'); }); return typeof window.onairos !== 'undefined'; }; const validateDomain = async () => { // Your logic to validate the domain goes here // For example, you could make an API request to your backend return fetch('https://api2.onairos.uk/valid/validate-domain', { // return fetch('http://localhost:8080/valid/validate-domain', { method: 'POST', headers: { 'Content-Type': 'application/json', }, }).then(r => r.json().then(data => ({status: r.status, body: data}))) .catch(error => console.error(error)); }; const OnairosChecks = async () => { if (checkOnairosExtension()) { // The extension is installed // Proceed with the domain validation request or any other logic if((await validateDomain()).body.status){ // Valid Domain // Proceed with the domain validation request or any other logic try { validateRequestData(); await ConnectOnairos(); } catch (error) { // Handle any errors here console.error("Error connecting to Onairos", error); } }else{ console.error("Please make sure this is an Onairos Partnered app"); } } else { // The extension is not installed // Open the Chrome Web Store page to download the extension window.open('https://chromewebstore.google.com/detail/onairos/apkfageplidiblifhnadehmplfccapkf', '_blank'); } }; const OnairosPublicKey = ` -----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4wkWvRPaJiY8CwQ5BoJI amcGAYV91Bk8NrvWq4PXM+J/RJugfgTNCYKQ/c6g4xa1YES/tJEzFS7nf0Kdoqxm 5aav0ru5vS4fc4vCOLTI9W1T7nj02NY91rogsQm2/KMxUQ8DaLeTZKi+0Wjsa9YO 6XGGd1wh4azgQkj04MWW5J1EBCcBavKoY+C85oA9jkkklQ8nGWgbugmZs7eXHNQb qH8/ZHcB9Kx1CZ6XjQuVd6YE/A+swV+DksbkXANcYjr6SY/2TbB8GfpcOMM3bkyN Q8e0A51q5a8abfuAkDZXe67MwKMWu/626abwPZhJrKr5HhRZZDwPtnXlktYHhOK6 lQIDAQAB -----END PUBLIC KEY----- `; const domain = window.location.href; const openTerminal = async () => { if (isMobileDevice()) { // Testing await handleAPIRequestForMobile(); return; } console.log("openTerminal clicked"); window.postMessage({ source: 'webpage', type: 'openTerminal', webpageName: webpageName, domain: domain, key: "Key" }); }; useEffect(() => { // Listener to receive messages const handleMessage = (event) => { if (event.data && event.data.action === 'terminalOpened') { ConnectOnairos(); } }; window.addEventListener('message', handleMessage); return () => { window.removeEventListener('message', handleMessage); }; }, []); const ConnectOnairos = async () => { try{ const appInfo = { name: "Onairos", version: "1.0.0", env: "production", }; const othent = new Othent({ appInfo, throwErrors: false}); // Get User Othent Secure Details // const { connect} = await loadOthentKms(); const userDetails = await othent.connect(); const sha256 = await loadSha256(); const hashedOthentSub = sha256(userDetails.sub).toString(); const encryptedPin = await getPin(hashedOthentSub); console.log("Got your Pin"); function convertToBuffer(string) { try { // Decode base64 string const encodedData = window.atob(string); const uint8Array = new Uint8Array(encodedData.length); for (let i = 0; i < encodedData.length; i++) { uint8Array[i] = encodedData.charCodeAt(i); } return uint8Array.buffer; // This is an ArrayBuffer } catch (e) { console.error("Error converting to Buffer :", e); } } const bufferPIN = convertToBuffer(encryptedPin.result); // const {decrypt }= await loadOthentKms(); const userPin = await othent.decrypt(bufferPIN); // RSA Encrypt the PIN to transmit to Terminal and backend rsaEncrypt(OnairosPublicKey, userPin) .then(encryptedData => { // Prepare the data to be sent window.postMessage({ source: 'webpage', type: 'GET_API_URL', webpageName: webpageName, domain:domain, requestData: requestData, proofMode:proofMode, HashedOthentSub:hashedOthentSub, EncryptedUserPin:encryptedData }); }) .catch(error => { console.error("Encryption failed:", error); }); }catch(e){ console.error("Error Sending Data to Terminal: ", e); } }; // Styling and button class based on visual type and login mode const buttonClass = `flex items-center justify-center font-bold rounded cursor-pointer ${ buttonType === 'pill' ? 'px-4 py-2' : 'w-12 h-12' } ${login ? 'bg-white border border-gray-300' : 'bg-transparent'} ${ isMobileDevice()? '':'OnairosConnect'} ` ; const buttonStyle = { flexDirection: textLayout === 'below' ? 'column' : 'row', backgroundColor: login ? '#ffffff' : 'transparent', color: login ? 'black' : textColor, border: login ? '1px solid #ddd' : '1px solid transparent', }; // Icon and text style based on the visualType const logoStyle = { width: '20px', height: '20px', marginRight: visualType === 'full' ? '12px' : '0', // Space between icon and text only in full mode }; const getText = () => { switch (loginType) { case 'signUp': return 'Sign Up with Onairos'; case 'signOut': return 'Sign Out of Onairos'; default: return 'Sign In with Onairos'; } }; // Make sure you have this environment variable set const fetchAccountInfo = async (identifier, isEmail = false) => { try { const jsonData = isEmail? { Info:{ identifier: identifier } } : { Info:{ userName:identifier } }; const endpoint = isEmail ? '/getAccountInfo/email' : '/getAccountInfo'; const response = await fetch(`${API_URL}${endpoint}`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${localStorage.getItem('onairosToken')}` }, body: JSON.stringify(jsonData) }); if (!response.ok) { throw new Error('Failed to fetch account info'); } const data = await response.json(); if (data.AccountInfo === "No Account Found") { NoAccount.current = true; setAccountInfo(null); return null; } setAccountInfo(data.AccountInfo); if (data.AccountInfo.models) { setActiveModels(data.AccountInfo.models); } else { NoModel.current = true; } if (data.AccountInfo.avatar) { setAvatar(true); } if (data.AccountInfo.UserTraits) { setTraits(true); } if (data.AccountInfo.othent) { setOthentUser(true); } // // If we have account info and models, show the overlay with data requests // if (data.AccountInfo && data.AccountInfo.models?.length > 0) { // setShowOverlay(true); // } return data.AccountInfo; } catch (error) { console.error('Error fetching account info:', error); throw error; } }; const checkExistingToken = async () => { try { const onairosToken = localStorage.getItem('onairosToken'); const legacyToken = localStorage.getItem('token'); const token = onairosToken || legacyToken; if (token) { const response = await fetch('https://api2.onairos.uk/verifyToken', { // const response = await fetch('http://localhost:8080/verifyToken', { headers: { 'Authorization': `Bearer ${token}` } }); if (response.ok) { const data = await response.json(); if (data.valid) { setAuthToken(token); setIsAuthenticated(true); const username = localStorage.getItem('username'); await fetchAccountInfo(username); } else { localStorage.removeItem('onairosToken'); localStorage.removeItem('token'); } } } } catch (error) { console.error('Token verification failed:', error); } finally { setLoading(false); } }; useEffect(() => { if (isMobileDevice()) { checkExistingToken(); } }, []); const handleCloseOverlay = () => { setGranted(0); setShowOverlay(false); }; // Check for existing token and fetch account info on mount useEffect(() => { const token = localStorage.getItem('onairosToken'); const username = localStorage.getItem('username'); if (token && username) { fetchAccountInfo(username, false); } }, []); const handleLoginSuccess = async (identifier, isEmail = false) => { try { const accountData = await fetchAccountInfo(identifier, isEmail); // Update authentication first setIsAuthenticated(true); // Then update account info setShowOverlay(true); // setAccountInfo(accountData); return accountData; } catch (error) { console.error('Login process failed:', error); throw error; } }; // Check for stored tokens on mount useEffect(() => { const checkStoredAuth = async () => { const token = localStorage.getItem('onairosToken'); const username = localStorage.getItem('username'); const othentToken = localStorage.getItem('othentToken'); if (token) { try { // Verify token is still valid const response = await fetch(`${API_URL}/verifyToken`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` } }); if (response.ok) { setIsAuthenticated(true); if (username) { await fetchAccountInfo(username, false); } else if (othentToken) { // Handle Othent stored session const userDetails = JSON.parse(othentToken); await fetchAccountInfo(userDetails.email, true); } } else { // Clear invalid tokens localStorage.removeItem('onairosToken'); localStorage.removeItem('username'); localStorage.removeItem('othentToken'); } } catch (error) { console.error('Token verification failed:', error); } } }; checkStoredAuth(); }, []); return ( <> <div className="flex items-center justify-center"> <button className={buttonClass} onClick={openTerminal} style={buttonStyle} > {/* Render based on visualType prop */} {(visualType === 'full' || visualType === 'icon') && ( <img src={login ? "https://onairos.sirv.com/Images/OnairosWhite.png" : "https://onairos.sirv.com/Images/OnairosBlack.png"} alt="Onairos Logo" style={logoStyle} className={`${buttonType === 'pill' ? 'w-6 h-6' : 'w-8 h-8'} object-contain`} /> )} {/* Only render text if visualType is 'full' or 'textOnly' */} {(visualType === 'full' || visualType === 'textOnly') && ( <span className={`${login ? 'text-black' : textColor === 'black' ? 'text-black' : 'text-white'} ${visualType === 'icon' ? 'sr-only' : ''} ${textLayout === 'right' ? 'ml-2' : textLayout === 'left' ? 'mr-2' : ''}`}> {getText()} </span> )} </button> </div> {/* {notif.show && <Notification message={notif.message} color={notif.color} />} */} {authDialog.show && // {false && ( <div className="fixed inset-0 z-50 flex items-center justify-center"> <div className="fixed inset-0 bg-black bg-opacity-50" onClick={() => setAuthDialog({ show: false, type: null, data: null })} /> <div className="relative bg-white rounded-lg p-6 max-w-lg w-full mx-4 max-h-[80vh] overflow-y-auto"> <button onClick={() => setAuthDialog({ show: false, type: null, data: null })} className="absolute top-4 right-4 text-gray-400 hover:text-gray-600" > <span className="sr-only">Close</span> <svg className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" /> </svg> </button> <h3 className="text-lg font-semibold mb-4"> {authDialog.type === 'callback' ? 'Callback Details' : 'Authentication Result'} </h3> <h3 className="text-lg font-semibold mb-4"> {authDialog.type === 'debug' ? 'Debug Results' : 'Authentication Result'} </h3> <h3 className="text-lg font-semibold mb-4"> {authDialog.type === 'apiURL' ? 'API Url Returned' : 'Authentication Result'} </h3> <div className="bg-gray-50 rounded p-4 overflow-x-auto"> <pre className="text-sm"> {JSON.stringify(authDialog.data, null, 2)} </pre> </div> {authDialog.type === 'auth' && ( <div className={`mt-4 p-3 rounded ${authDialog.data?.success ? 'bg-green-100 text-green-700' : 'bg-red-100 text-red-700'}`}> {authDialog.data?.success ? 'Authentication successful!' : 'Authentication failed'} </div> )} </div> </div> )} {isLoading && ( <div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50"> <div className="bg-white p-6 rounded-lg shadow-xl"> <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-500 mx-auto"></div> <p className="mt-4 text-gray-600">Loading your account...</p> </div> </div> )} {showOverlay && !isLoading && ( <Overlay setOthentConnected={setOthentConnected} dataRequester={webpageName} NoAccount={NoAccount} NoModel={NoModel} accountInfo={accountInfo} activeModels={activeModels} avatar={avatar} traits={traits} requestData={requestData} handleConnectionSelection={handleConnectionSelection} changeGranted={changeGranted} granted={granted} allowSubmit={granted > 0} rejectDataRequest={rejectDataRequest} sendDataRequest={sendDataRequest} isAuthenticated={isAuthenticated} onLoginSuccess={handleLoginSuccess} onClose={handleCloseOverlay} setOthentUser={setOthentUser} setHashedOthentSub={setHashedOthentSub} setEncryptedPin={setEncryptedPin} /> )} </> ) } export default OnairosButton;