andito's picture
andito HF Staff
update web
c29203c
/**
* Hugging Face OAuth Authentication and Space Management
* Uses @huggingface/hub library for OAuth flow
*
* Note: The public space now supports secure multi-user connections with per-user isolation.
* Cloning a private space is optional - most users can use the public space safely.
*/
import { oauthLoginUrl, oauthHandleRedirectIfPresent, uploadFile, createRepo } from "https://cdn.jsdelivr.net/npm/@huggingface/hub@0.15.2/+esm";
const HF_API = 'https://huggingface.co/api';
const NEW_SPACE_NAME = 'reachy_mini_remote_control';
// State management
let currentUser = null;
let userToken = null;
let oauthResult = null;
/**
* Initialize OAuth and check authentication status
*/
async function initAuth() {
try {
// Check if we're returning from OAuth redirect
oauthResult = await oauthHandleRedirectIfPresent();
if (oauthResult) {
// User just authenticated via OAuth
console.log('OAuth result:', oauthResult);
console.log('User info:', oauthResult.userInfo);
console.log('All userInfo keys:', Object.keys(oauthResult.userInfo));
// Extract username from fullname field (HuggingFace uses fullname for the username)
currentUser = oauthResult.userInfo.fullname;
userToken = oauthResult.accessToken;
console.log('Extracted username:', currentUser);
console.log('Extracted token:', userToken ? 'present' : 'missing');
// Store for session (not persistent across page reloads by design)
sessionStorage.setItem('hf_token', userToken);
sessionStorage.setItem('hf_username', currentUser);
sessionStorage.setItem('hf_token_expires', oauthResult.accessTokenExpiresAt);
console.log('OAuth authentication successful:', currentUser);
showAuthenticatedView();
await checkForExistingSpace();
} else {
// Check if we have a stored session
const storedToken = sessionStorage.getItem('hf_token');
const storedUser = sessionStorage.getItem('hf_username');
const tokenExpires = sessionStorage.getItem('hf_token_expires');
if (storedToken && storedUser && tokenExpires && new Date(tokenExpires) > new Date()) {
// Valid session exists
userToken = storedToken;
currentUser = storedUser;
showAuthenticatedView();
await checkForExistingSpace();
}
}
} catch (error) {
console.error('OAuth initialization error:', error);
// Show login view on error
showLoginView();
}
}
/**
* Redirect to Hugging Face OAuth login
*/
async function loginToHuggingFace() {
try {
// Generate OAuth login URL and redirect
const loginUrl = await oauthLoginUrl();
window.location.href = loginUrl;
} catch (error) {
console.error('OAuth login error:', error);
alert('Failed to initiate login. Please try again.');
}
}
/**
* Show the login view
*/
function showLoginView() {
document.getElementById('login-view').style.display = 'block';
document.getElementById('authenticated-view').style.display = 'none';
}
/**
* Show authenticated user view
*/
function showAuthenticatedView() {
console.log('Showing authenticated view for user:', currentUser);
document.getElementById('login-view').style.display = 'none';
document.getElementById('authenticated-view').style.display = 'block';
// Display username (from fullname field)
document.getElementById('username').textContent = `@${currentUser}`;
}
/**
* Check if user already has the space
*/
async function checkForExistingSpace() {
try {
const response = await fetch(`${HF_API}/spaces/${currentUser}/${NEW_SPACE_NAME}`, {
headers: {
'Authorization': `Bearer ${userToken}`
}
});
if (response.ok) {
showExistingSpaceView();
} else {
showNoSpaceView();
}
} catch (error) {
console.error('Error checking for space:', error);
showNoSpaceView();
}
}
/**
* Show view for users who don't have the space yet
*/
function showNoSpaceView() {
document.getElementById('no-space-view').style.display = 'block';
document.getElementById('has-space-view').style.display = 'none';
}
/**
* Show view for users who already have the space
*/
function showExistingSpaceView() {
document.getElementById('no-space-view').style.display = 'none';
document.getElementById('has-space-view').style.display = 'block';
// Update space URL displays
const spaceUrl = `${currentUser}/${NEW_SPACE_NAME}`;
// HuggingFace converts underscores to hyphens in subdomain URLs
// Example: andito/reachy_mini_remote_control -> andito-reachy-mini-remote-control.hf.space
const wsUri = `wss://${currentUser}-${NEW_SPACE_NAME.replace(/_/g, '-')}.hf.space`;
document.getElementById('space-url-display').textContent = spaceUrl;
document.getElementById('private-uri').textContent = wsUri;
document.getElementById('space-link').href = `https://huggingface.co/spaces/${spaceUrl}`;
}
/**
* Create a new space and upload files
*/
async function cloneSpace() {
const btn = document.querySelector('#no-space-view .btn');
const btnText = document.getElementById('clone-btn-text');
const originalText = btnText.textContent;
try {
// Verify we have user credentials
if (!currentUser || !userToken) {
throw new Error('Not authenticated. Please sign in again.');
}
// Update button state
btn.disabled = true;
btnText.textContent = '⏳ Creating space...';
console.log('Creating space for user:', currentUser);
console.log('Creating repo:', `${currentUser}/${NEW_SPACE_NAME}`);
// Step 1: Create the space repository using @huggingface/hub
const createResult = await createRepo({
repo: {
type: 'space',
name: `${currentUser}/${NEW_SPACE_NAME}`
},
accessToken: userToken,
credentials: {
accessToken: userToken
},
hubUrl: 'https://huggingface.co',
spaceHardware: 'cpu-basic',
spaceSdk: 'gradio',
private: true
});
console.log('Repo created successfully:', createResult);
btnText.textContent = '⏳ Uploading files...';
// Step 2: Upload files to the space from local directory
const files = ['README.md', 'app.py', 'requirements.txt', 'packages.txt', 'dist.zip'];
for (const fileName of files) {
console.log(`Fetching ${fileName}...`);
// Fetch file content from local remote_control_space directory
const fileResponse = await fetch(`remote_control_space/${fileName}`);
if (!fileResponse.ok) {
console.error(`Failed to fetch ${fileName}`);
continue;
}
const content = await fileResponse.blob();
console.log(`Uploading ${fileName}...`);
// Upload to space using @huggingface/hub
const uploadResult = await uploadFile({
repo: {
type: 'space',
name: `${currentUser}/${NEW_SPACE_NAME}`
},
accessToken: userToken,
credentials: {
accessToken: userToken
},
file: {
path: fileName,
content: content
}
});
console.log(`${fileName} uploaded:`, uploadResult);
}
btnText.textContent = '✓ Space created successfully!';
// Wait a moment then show the space view
setTimeout(() => {
showExistingSpaceView();
}, 1500);
} catch (error) {
console.error('Error creating space:', error);
alert(`Failed to create space: ${error.message}\n\nPlease try again or create the space manually.`);
btn.disabled = false;
btnText.textContent = originalText;
}
}
/**
* Logout and clear stored credentials
*/
function logout() {
if (confirm('Are you sure you want to sign out?')) {
localStorage.removeItem('hf_token');
localStorage.removeItem('hf_username');
currentUser = null;
userToken = null;
// Reset UI
document.getElementById('login-view').style.display = 'block';
document.getElementById('authenticated-view').style.display = 'none';
}
}
/**
* Copy text to clipboard
*/
function copyToClipboard(text, buttonElement) {
navigator.clipboard.writeText(text).then(() => {
// Visual feedback
if (buttonElement) {
const originalText = buttonElement.textContent;
buttonElement.textContent = '✓ Copied!';
setTimeout(() => {
buttonElement.textContent = originalText;
}, 2000);
}
}).catch(err => {
console.error('Failed to copy:', err);
alert('Failed to copy to clipboard');
});
}
/**
* Copy private URI to clipboard
*/
function copyPrivateUri(event) {
const uri = document.getElementById('private-uri').textContent;
copyToClipboard(uri, event.currentTarget);
}
// Expose functions globally for onclick handlers
window.loginToHuggingFace = loginToHuggingFace;
window.cloneSpace = cloneSpace;
window.logout = logout;
window.copyToClipboard = copyToClipboard;
window.copyPrivateUri = copyPrivateUri;
// Initialize on page load
document.addEventListener('DOMContentLoaded', initAuth);