Link Device

Enter the 12-character connection code from your account dashboard.

// // // // Make sure a user signs in via Google/other providers before reaching this page // (or implement sign-in flow directly on this page if not handled elsewhere). // This script will assume `firebase.auth()` and `firebase.firestore()` are available. // If using modular SDK, imports would look like: // import { getAuth, onAuthStateChanged } from 'firebase/auth'; // import { getFirestore, collection, query, where, getDocs } from 'firebase/firestore'; document.addEventListener('DOMContentLoaded', () => { const generateCodeBtn = document.getElementById('generateCodeBtn'); const messageElement = document.getElementById('message'); const generatedCodeDisplay = document.getElementById('generatedCodeDisplay'); const displayCodeInput = document.getElementById('displayCode'); const copyCodeBtn = document.getElementById('copyCodeBtn'); const codeExpiryElement = document.getElementById('codeExpiry'); const manageCodesSection = document.getElementById('manageCodesSection'); const activeCodesList = document.getElementById('activeCodesList'); // TODO: Update these endpoints to your deployed Cloud Functions URLs const CLOUD_FUNCTIONS_BASE_URL = 'https://us-central1-project-zirconium.cloudfunctions.net'; const GENERATE_CODE_ENDPOINT = `${CLOUD_FUNCTIONS_BASE_URL}/generateCode`; const REVOKE_CODE_ENDPOINT = `${CLOUD_FUNCTIONS_BASE_URL}/revokeCode`; // Function to get Firebase ID token, if authenticated async function getAuthToken() { try { // Check if Firebase Auth is initialized and a user is signed in if (typeof firebase !== 'undefined' && firebase.auth() && firebase.auth().currentUser) { return await firebase.auth().currentUser.getIdToken(); } else { // console.warn('Firebase Auth not initialized or user not signed in.'); return null; } } catch (error) { console.error('Error getting auth token:', error); return null; } } generateCodeBtn.addEventListener('click', async () => { messageElement.textContent = 'Generating code...'; messageElement.className = 'mt-4 text-center text-sm text-yellow-500'; generatedCodeDisplay.classList.add('hidden'); try { const idToken = await getAuthToken(); if (!idToken) { messageElement.textContent = 'Please sign in to generate a code.'; messageElement.className = 'mt-4 text-center text-sm text-red-500'; return; } const response = await fetch(GENERATE_CODE_ENDPOINT, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${idToken}` // Include auth token for function authorization }, body: JSON.stringify({}) }); if (!response.ok) { const errorData = await response.json(); throw new Error(errorData.message || 'Failed to generate code.'); } const data = await response.json(); if (data.success) { displayCodeInput.value = data.code; codeExpiryElement.textContent = `Expires: ${new Date(data.expiresAt).toLocaleString()}`; messageElement.textContent = ''; generatedCodeDisplay.classList.remove('hidden'); displayCodeInput.select(); displayCodeInput.setSelectionRange(0, 99999); try { await navigator.clipboard.writeText(data.code); messageElement.textContent = 'Code generated and copied to clipboard!'; messageElement.className = 'mt-4 text-center text-sm text-green-500'; } catch (err) { console.warn('Failed to copy to clipboard automatically:', err); messageElement.textContent = 'Code generated. Please copy it manually.'; messageElement.className = 'mt-4 text-center text-sm text-blue-500'; } // Refresh the list of active codes await fetchActiveCodes(); } else { throw new Error(data.message || 'Unknown error during code generation.'); } } catch (error) { console.error('Error generating code:', error); messageElement.textContent = `Error: ${error.message}`; messageElement.className = 'mt-4 text-center text-sm text-red-500'; } }); copyCodeBtn.addEventListener('click', async () => { try { await navigator.clipboard.writeText(displayCodeInput.value); messageElement.textContent = 'Code copied to clipboard!'; messageElement.className = 'mt-4 text-center text-sm text-green-500'; } catch (err) { console.error('Failed to copy code:', err); messageElement.textContent = 'Failed to copy code. Please select and copy manually.'; messageElement.className = 'mt-4 text-center text-sm text-red-500'; } }); // Function to fetch and display active codes for the current user async function fetchActiveCodes() { activeCodesList.innerHTML = '

Loading codes...

'; manageCodesSection.classList.remove('hidden'); try { const idToken = await getAuthToken(); if (!idToken || !firebase.auth().currentUser) { activeCodesList.innerHTML = '

Please sign in to view your codes.

'; return; } // Direct Firestore query example (requires security rule: allow read: if request.auth.uid == resource.data.userId; ) // This rule would need to be added to `other-required/firestore.rules` for this to work const codesSnapshot = await firebase.firestore().collection('unlockCodes') .where('userId', '==', firebase.auth().currentUser.uid) .where('used', '==', false) .where('revoked', '==', false) .get(); if (codesSnapshot.empty) { activeCodesList.innerHTML = '

No active codes found.

'; return; } activeCodesList.innerHTML = ''; // Clear loading message codesSnapshot.forEach(doc => { const codeData = doc.data(); const codeHash = doc.id; // The document ID is the code hash const expiration = codeData.expiresAt ? new Date(codeData.expiresAt.toDate()).toLocaleString() : 'Never'; const deviceStatus = codeData.deviceFingerprint ? 'Linked' : 'Not linked'; const codeItem = document.createElement('div'); codeItem.className = 'bg-custom-darkest-gray p-3 rounded-lg flex items-center justify-between'; codeItem.innerHTML = `

${codeHash.substring(0, 8)}...

Expires: ${expiration} | Device: ${deviceStatus}

`; activeCodesList.appendChild(codeItem); }); // Add event listeners for revoke buttons document.querySelectorAll('.revoke-btn').forEach(button => { button.addEventListener('click', async (event) => { const hashToRevoke = event.target.dataset.codeHash; messageElement.textContent = `Revoking code ${hashToRevoke.substring(0, 8)}...`; messageElement.className = 'mt-4 text-center text-sm text-yellow-500'; try { const idToken = await getAuthToken(); if (!idToken) { messageElement.textContent = 'Please sign in to revoke codes.'; messageElement.className = 'mt-4 text-center text-sm text-red-500'; return; } const response = await fetch(REVOKE_CODE_ENDPOINT, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${idToken}` }, body: JSON.stringify({ codeHash: hashToRevoke }) }); if (!response.ok) { const errorData = await response.json(); throw new Error(errorData.message || 'Failed to revoke code.'); } const data = await response.json(); if (data.success) { messageElement.textContent = `Code ${hashToRevoke.substring(0, 8)} revoked successfully!`; messageElement.className = 'mt-4 text-center text-sm text-green-500'; await fetchActiveCodes(); // Refresh list } else { throw new Error(data.message || 'Unknown error during revocation.'); } } catch (error) { console.error('Error revoking code:', error); messageElement.textContent = `Error: ${error.message}`; messageElement.className = 'mt-4 text-center text-sm text-red-500'; } }); }); } catch (error) { console.error('Error fetching active codes:', error); activeCodesList.innerHTML = `

Error loading codes: ${error.message}. Ensure Firestore rules permit access.

`; } } // Initial load of active codes // We need to wait for Firebase Auth to be ready. if (typeof firebase !== 'undefined' && firebase.auth()) { firebase.auth().onAuthStateChanged(user => { if (user) { fetchActiveCodes(); } else { activeCodesList.innerHTML = '

Sign in to manage your codes.

'; manageCodesSection.classList.remove('hidden'); } }); } else { activeCodesList.innerHTML = '

Firebase Auth not available. Cannot manage codes.

'; manageCodesSection.classList.remove('hidden'); } });