259 lines
8.1 KiB
TypeScript
259 lines
8.1 KiB
TypeScript
import * as odd from '@oddjs/odd';
|
|
import type FileSystem from '@oddjs/odd/fs/index';
|
|
import { asyncDebounce } from '../utils/asyncDebounce';
|
|
import * as browser from '../utils/browser';
|
|
import { DIRECTORIES } from '../../context/FileSystemContext';
|
|
|
|
/**
|
|
* Constants for filesystem paths
|
|
*/
|
|
export const ACCOUNT_SETTINGS_DIR = ['private', 'settings'];
|
|
export const GALLERY_DIRS = {
|
|
PUBLIC: ['public', 'gallery'],
|
|
PRIVATE: ['private', 'gallery']
|
|
};
|
|
export const AREAS = {
|
|
PUBLIC: 'public',
|
|
PRIVATE: 'private'
|
|
};
|
|
|
|
/**
|
|
* Checks if a username is valid according to ODD's rules
|
|
* @param username The username to check
|
|
* @returns A boolean indicating if the username is valid
|
|
*/
|
|
export const isUsernameValid = async (username: string): Promise<boolean> => {
|
|
console.log('Checking if username is valid:', username);
|
|
try {
|
|
// Fallback if ODD account functions are not available
|
|
if (odd.account && odd.account.isUsernameValid) {
|
|
const isValid = await odd.account.isUsernameValid(username);
|
|
console.log('Username validity check result:', isValid);
|
|
return Boolean(isValid);
|
|
}
|
|
// Default validation if ODD is not available
|
|
const usernameRegex = /^[a-zA-Z0-9_-]{3,20}$/;
|
|
const isValid = usernameRegex.test(username);
|
|
console.log('Username validity check result (fallback):', isValid);
|
|
return isValid;
|
|
} catch (error) {
|
|
console.error('Error checking username validity:', error);
|
|
return false;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Debounced function to check if a username is available
|
|
*/
|
|
const debouncedIsUsernameAvailable = asyncDebounce(
|
|
(username: string) => {
|
|
// Fallback if ODD account functions are not available
|
|
if (odd.account && odd.account.isUsernameAvailable) {
|
|
return odd.account.isUsernameAvailable(username);
|
|
}
|
|
// Default to true if ODD is not available
|
|
return Promise.resolve(true);
|
|
},
|
|
300
|
|
);
|
|
|
|
/**
|
|
* Checks if a username is available
|
|
* @param username The username to check
|
|
* @returns A boolean indicating if the username is available
|
|
*/
|
|
export const isUsernameAvailable = async (
|
|
username: string
|
|
): Promise<boolean> => {
|
|
console.log('Checking if username is available:', username);
|
|
try {
|
|
// In a local development environment, simulate the availability check
|
|
// by checking if the username exists in localStorage
|
|
if (browser.isBrowser()) {
|
|
const isAvailable = await browser.isUsernameAvailable(username);
|
|
console.log('Username availability check result:', isAvailable);
|
|
return isAvailable;
|
|
} else {
|
|
// If not in a browser (SSR), use the ODD API
|
|
const isAvailable = await debouncedIsUsernameAvailable(username);
|
|
console.log('Username availability check result:', isAvailable);
|
|
return Boolean(isAvailable);
|
|
}
|
|
} catch (error) {
|
|
console.error('Error checking username availability:', error);
|
|
return false;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Create additional directories and files needed by the app
|
|
* @param fs FileSystem
|
|
*/
|
|
export const initializeFilesystem = async (fs: FileSystem): Promise<void> => {
|
|
try {
|
|
// Create required directories
|
|
console.log('Creating required directories...');
|
|
|
|
// Fallback if ODD path is not available
|
|
if (!odd.path || !odd.path.directory) {
|
|
console.log('ODD path not available, skipping filesystem initialization');
|
|
return;
|
|
}
|
|
|
|
// Public directories
|
|
await fs.mkdir(odd.path.directory(...DIRECTORIES.PUBLIC.ROOT));
|
|
await fs.mkdir(odd.path.directory(...DIRECTORIES.PUBLIC.GALLERY));
|
|
await fs.mkdir(odd.path.directory(...DIRECTORIES.PUBLIC.DOCUMENTS));
|
|
|
|
// Private directories
|
|
await fs.mkdir(odd.path.directory(...DIRECTORIES.PRIVATE.ROOT));
|
|
await fs.mkdir(odd.path.directory(...DIRECTORIES.PRIVATE.GALLERY));
|
|
await fs.mkdir(odd.path.directory(...DIRECTORIES.PRIVATE.SETTINGS));
|
|
await fs.mkdir(odd.path.directory(...DIRECTORIES.PRIVATE.DOCUMENTS));
|
|
|
|
console.log('Filesystem initialized successfully');
|
|
} catch (error) {
|
|
console.error('Error during filesystem initialization:', error);
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Checks data root for a username with retries
|
|
* @param username The username to check
|
|
*/
|
|
export const checkDataRoot = async (username: string): Promise<void> => {
|
|
console.log('Looking up data root for username:', username);
|
|
|
|
// Fallback if ODD dataRoot is not available
|
|
if (!odd.dataRoot || !odd.dataRoot.lookup) {
|
|
console.log('ODD dataRoot not available, skipping data root lookup');
|
|
return;
|
|
}
|
|
|
|
let dataRoot = await odd.dataRoot.lookup(username);
|
|
console.log('Initial data root lookup result:', dataRoot ? 'found' : 'not found');
|
|
|
|
if (dataRoot) return;
|
|
|
|
console.log('Data root not found, starting retry process...');
|
|
return new Promise((resolve, reject) => {
|
|
const maxRetries = 20;
|
|
let attempt = 0;
|
|
|
|
const dataRootInterval = setInterval(async () => {
|
|
console.warn(`Could not fetch filesystem data root. Retrying (${attempt + 1}/${maxRetries})`);
|
|
|
|
dataRoot = await odd.dataRoot.lookup(username);
|
|
console.log(`Retry ${attempt + 1} result:`, dataRoot ? 'found' : 'not found');
|
|
|
|
if (!dataRoot && attempt < maxRetries) {
|
|
attempt++;
|
|
return;
|
|
}
|
|
|
|
console.log(`Retry process completed. Data root ${dataRoot ? 'found' : 'not found'} after ${attempt + 1} attempts`);
|
|
clearInterval(dataRootInterval);
|
|
|
|
if (dataRoot) {
|
|
resolve();
|
|
} else {
|
|
reject(new Error(`Data root not found after ${maxRetries} attempts`));
|
|
}
|
|
}, 500);
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Generate a cryptographic key pair and store in localStorage during registration
|
|
* @param username The username being registered
|
|
*/
|
|
export const generateUserCredentials = async (username: string): Promise<boolean> => {
|
|
if (!browser.isBrowser()) return false;
|
|
|
|
try {
|
|
console.log('Generating cryptographic keys for user...');
|
|
// Generate a key pair using Web Crypto API
|
|
const keyPair = await browser.generateKeyPair();
|
|
|
|
if (!keyPair) {
|
|
console.error('Failed to generate key pair');
|
|
return false;
|
|
}
|
|
|
|
// Export the public key
|
|
const publicKeyBase64 = await browser.exportPublicKey(keyPair.publicKey);
|
|
|
|
if (!publicKeyBase64) {
|
|
console.error('Failed to export public key');
|
|
return false;
|
|
}
|
|
|
|
console.log('Keys generated successfully');
|
|
|
|
// Store the username and public key
|
|
browser.addRegisteredUser(username);
|
|
browser.storePublicKey(username, publicKeyBase64);
|
|
|
|
return true;
|
|
} catch (error) {
|
|
console.error('Error generating user credentials:', error);
|
|
return false;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Validate a user's stored credentials (for development mode)
|
|
* @param username The username to validate
|
|
*/
|
|
export const validateStoredCredentials = (username: string): boolean => {
|
|
if (!browser.isBrowser()) return false;
|
|
|
|
try {
|
|
const users = browser.getRegisteredUsers();
|
|
const publicKey = browser.getPublicKey(username);
|
|
|
|
return users.includes(username) && Boolean(publicKey);
|
|
} catch (error) {
|
|
console.error('Error validating stored credentials:', error);
|
|
return false;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Register a new user with the specified username
|
|
* @param username The username to register
|
|
* @returns A boolean indicating if registration was successful
|
|
*/
|
|
export const register = async (username: string): Promise<boolean> => {
|
|
try {
|
|
console.log('Registering user:', username);
|
|
|
|
// Check if username is valid
|
|
const isValid = await isUsernameValid(username);
|
|
if (!isValid) {
|
|
console.error('Invalid username format');
|
|
return false;
|
|
}
|
|
|
|
// Check if username is available
|
|
const isAvailable = await isUsernameAvailable(username);
|
|
if (!isAvailable) {
|
|
console.error('Username is not available');
|
|
return false;
|
|
}
|
|
|
|
// Generate user credentials
|
|
const credentialsGenerated = await generateUserCredentials(username);
|
|
if (!credentialsGenerated) {
|
|
console.error('Failed to generate user credentials');
|
|
return false;
|
|
}
|
|
|
|
console.log('User registration successful');
|
|
return true;
|
|
} catch (error) {
|
|
console.error('Error during user registration:', error);
|
|
return false;
|
|
}
|
|
}; |