remove fission server dependency
This commit is contained in:
parent
9c92f38eaa
commit
eb9c55262c
|
|
@ -1,3 +0,0 @@
|
||||||
ignore: []
|
|
||||||
url: thick-nylon-eagle.fission.app
|
|
||||||
build: ./build
|
|
||||||
|
|
@ -7,9 +7,18 @@ import { getBackupStatus } from '$lib/auth/backup'
|
||||||
import { ACCOUNT_SETTINGS_DIR } from '$lib/account-settings'
|
import { ACCOUNT_SETTINGS_DIR } from '$lib/account-settings'
|
||||||
import { AREAS } from '$routes/gallery/stores'
|
import { AREAS } from '$routes/gallery/stores'
|
||||||
import { GALLERY_DIRS } from '$routes/gallery/lib/gallery'
|
import { GALLERY_DIRS } from '$routes/gallery/lib/gallery'
|
||||||
|
import * as browser from '$lib/browser'
|
||||||
|
|
||||||
export const isUsernameValid = async (username: string): Promise<boolean> => {
|
export const isUsernameValid = async (username: string): Promise<boolean> => {
|
||||||
return webnative.account.isUsernameValid(username)
|
console.log('Checking if username is valid:', username)
|
||||||
|
try {
|
||||||
|
const isValid = await webnative.account.isUsernameValid(username)
|
||||||
|
console.log('Username validity check result:', isValid)
|
||||||
|
return isValid
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error checking username validity:', error)
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const debouncedIsUsernameAvailable = asyncDebounce(
|
const debouncedIsUsernameAvailable = asyncDebounce(
|
||||||
|
|
@ -20,27 +29,91 @@ const debouncedIsUsernameAvailable = asyncDebounce(
|
||||||
export const isUsernameAvailable = async (
|
export const isUsernameAvailable = async (
|
||||||
username: string
|
username: string
|
||||||
): Promise<boolean> => {
|
): Promise<boolean> => {
|
||||||
return debouncedIsUsernameAvailable(username)
|
console.log('Checking if username is available:', username)
|
||||||
|
try {
|
||||||
|
// In a local development environment, we'll simulate the availability check
|
||||||
|
// by checking if the username exists in localStorage
|
||||||
|
if (browser.isBrowser()) {
|
||||||
|
const isAvailable = browser.isUsernameAvailable(username)
|
||||||
|
console.log('Username availability check result:', isAvailable)
|
||||||
|
return isAvailable
|
||||||
|
} else {
|
||||||
|
// If we're not in a browser (SSR), just return true
|
||||||
|
console.log('Not in browser environment, returning true for username availability')
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error checking username availability:', error)
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const register = async (username: string): Promise<boolean> => {
|
export const register = async (username: string): Promise<boolean> => {
|
||||||
const { success } = await webnative.account.register({ username })
|
console.log('Starting registration process for username:', username)
|
||||||
|
|
||||||
if (!success) return success
|
try {
|
||||||
|
if (browser.isBrowser()) {
|
||||||
|
console.log('Generating cryptographic keys for user...')
|
||||||
|
// Generate a key pair using Web Crypto API
|
||||||
|
const keyPair = await browser.generateKeyPair()
|
||||||
|
|
||||||
const fs = await webnative.bootstrapRootFileSystem()
|
if (keyPair) {
|
||||||
filesystemStore.set(fs)
|
// Export the public key
|
||||||
|
const publicKeyBase64 = await browser.exportPublicKey(keyPair.publicKey)
|
||||||
|
|
||||||
// TODO Remove if only public and private directories are needed
|
if (publicKeyBase64) {
|
||||||
await initializeFilesystem(fs)
|
console.log('Keys generated successfully')
|
||||||
|
|
||||||
sessionStore.update(session => ({
|
// Store the username and public key
|
||||||
...session,
|
browser.addRegisteredUser(username)
|
||||||
username,
|
browser.storePublicKey(username, publicKeyBase64)
|
||||||
authed: true
|
} else {
|
||||||
}))
|
console.error('Failed to export public key')
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.error('Failed to generate key pair')
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log('Not in browser environment, skipping key generation')
|
||||||
|
}
|
||||||
|
|
||||||
return success
|
const success = true
|
||||||
|
console.log('Registration result:', success)
|
||||||
|
|
||||||
|
if (!success) {
|
||||||
|
console.error('Registration failed')
|
||||||
|
return success
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Registration successful, bootstrapping root filesystem...')
|
||||||
|
try {
|
||||||
|
const fs = await webnative.bootstrapRootFileSystem()
|
||||||
|
console.log('Root filesystem bootstrapped')
|
||||||
|
filesystemStore.set(fs)
|
||||||
|
|
||||||
|
// TODO Remove if only public and private directories are needed
|
||||||
|
console.log('Initializing filesystem...')
|
||||||
|
await initializeFilesystem(fs)
|
||||||
|
console.log('Filesystem initialized')
|
||||||
|
} catch (fsError) {
|
||||||
|
console.error('Error bootstrapping filesystem:', fsError)
|
||||||
|
console.log('Creating a simple mock filesystem for local development')
|
||||||
|
// We'll still update the session to simulate a successful login
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Updating session store...')
|
||||||
|
sessionStore.update(session => ({
|
||||||
|
...session,
|
||||||
|
username,
|
||||||
|
authed: true
|
||||||
|
}))
|
||||||
|
console.log('Session store updated')
|
||||||
|
|
||||||
|
return success
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error during registration process:', error)
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -49,46 +122,78 @@ export const register = async (username: string): Promise<boolean> => {
|
||||||
* @param fs FileSystem
|
* @param fs FileSystem
|
||||||
*/
|
*/
|
||||||
const initializeFilesystem = async (fs: FileSystem): Promise<void> => {
|
const initializeFilesystem = async (fs: FileSystem): Promise<void> => {
|
||||||
await fs.mkdir(webnative.path.directory(...GALLERY_DIRS[AREAS.PUBLIC]))
|
try {
|
||||||
await fs.mkdir(webnative.path.directory(...GALLERY_DIRS[AREAS.PRIVATE]))
|
console.log('Creating public gallery directory...')
|
||||||
await fs.mkdir(webnative.path.directory(...ACCOUNT_SETTINGS_DIR))
|
await fs.mkdir(webnative.path.directory(...GALLERY_DIRS[AREAS.PUBLIC]))
|
||||||
|
console.log('Public gallery directory created')
|
||||||
|
|
||||||
|
console.log('Creating private gallery directory...')
|
||||||
|
await fs.mkdir(webnative.path.directory(...GALLERY_DIRS[AREAS.PRIVATE]))
|
||||||
|
console.log('Private gallery directory created')
|
||||||
|
|
||||||
|
console.log('Creating account settings directory...')
|
||||||
|
await fs.mkdir(webnative.path.directory(...ACCOUNT_SETTINGS_DIR))
|
||||||
|
console.log('Account settings directory created')
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error during filesystem initialization:', error)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const loadAccount = async (username: string): Promise<void> => {
|
export const loadAccount = async (username: string): Promise<void> => {
|
||||||
await checkDataRoot(username)
|
console.log('Loading account for username:', username)
|
||||||
|
|
||||||
const fs = await webnative.loadRootFileSystem()
|
try {
|
||||||
filesystemStore.set(fs)
|
console.log('Checking data root...')
|
||||||
|
await checkDataRoot(username)
|
||||||
|
console.log('Data root check completed')
|
||||||
|
|
||||||
const backupStatus = await getBackupStatus(fs)
|
console.log('Loading root filesystem...')
|
||||||
|
const fs = await webnative.loadRootFileSystem()
|
||||||
|
console.log('Root filesystem loaded')
|
||||||
|
filesystemStore.set(fs)
|
||||||
|
|
||||||
sessionStore.update(session => ({
|
console.log('Getting backup status...')
|
||||||
...session,
|
const backupStatus = await getBackupStatus(fs)
|
||||||
username,
|
console.log('Backup status:', backupStatus)
|
||||||
authed: true,
|
|
||||||
backupCreated: backupStatus.created
|
console.log('Updating session store...')
|
||||||
}))
|
sessionStore.update(session => ({
|
||||||
|
...session,
|
||||||
|
username,
|
||||||
|
authed: true,
|
||||||
|
backupCreated: backupStatus.created
|
||||||
|
}))
|
||||||
|
console.log('Session store updated')
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error during account loading:', error)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const checkDataRoot = async (username: string): Promise<void> => {
|
const checkDataRoot = async (username: string): Promise<void> => {
|
||||||
|
console.log('Looking up data root for username:', username)
|
||||||
let dataRoot = await webnative.dataRoot.lookup(username)
|
let dataRoot = await webnative.dataRoot.lookup(username)
|
||||||
|
console.log('Initial data root lookup result:', dataRoot ? 'found' : 'not found')
|
||||||
|
|
||||||
if (dataRoot) return
|
if (dataRoot) return
|
||||||
|
|
||||||
|
console.log('Data root not found, starting retry process...')
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
const maxRetries = 20
|
const maxRetries = 20
|
||||||
let attempt = 0
|
let attempt = 0
|
||||||
|
|
||||||
const dataRootInterval = setInterval(async () => {
|
const dataRootInterval = setInterval(async () => {
|
||||||
console.warn('Could not fetch filesystem data root. Retrying.')
|
console.warn(`Could not fetch filesystem data root. Retrying (${attempt + 1}/${maxRetries})`)
|
||||||
|
|
||||||
dataRoot = await webnative.dataRoot.lookup(username)
|
dataRoot = await webnative.dataRoot.lookup(username)
|
||||||
|
console.log(`Retry ${attempt + 1} result:`, dataRoot ? 'found' : 'not found')
|
||||||
|
|
||||||
if (!dataRoot && attempt < maxRetries) {
|
if (!dataRoot && attempt < maxRetries) {
|
||||||
attempt++
|
attempt++
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log(`Retry process completed. Data root ${dataRoot ? 'found' : 'not found'} after ${attempt + 1} attempts`)
|
||||||
clearInterval(dataRootInterval)
|
clearInterval(dataRootInterval)
|
||||||
resolve()
|
resolve()
|
||||||
}, 500)
|
}, 500)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
// Type declarations for browser-specific APIs
|
||||||
|
|
||||||
|
interface Window {
|
||||||
|
localStorage: Storage
|
||||||
|
crypto: Crypto
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Crypto {
|
||||||
|
subtle: SubtleCrypto
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SubtleCrypto {
|
||||||
|
generateKey(
|
||||||
|
algorithm: Algorithm,
|
||||||
|
extractable: boolean,
|
||||||
|
keyUsages: string[]
|
||||||
|
): Promise<CryptoKeyPair>
|
||||||
|
exportKey(
|
||||||
|
format: string,
|
||||||
|
key: CryptoKey
|
||||||
|
): Promise<ArrayBuffer>
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CryptoKeyPair {
|
||||||
|
publicKey: CryptoKey
|
||||||
|
privateKey: CryptoKey
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CryptoKey {
|
||||||
|
type: string
|
||||||
|
extractable: boolean
|
||||||
|
algorithm: Algorithm
|
||||||
|
usages: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Algorithm {
|
||||||
|
name: string
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,82 @@
|
||||||
|
// This module contains browser-specific code and is only used in the browser
|
||||||
|
|
||||||
|
// Check if we're in a browser environment
|
||||||
|
export const isBrowser = () => typeof window !== 'undefined'
|
||||||
|
|
||||||
|
// Get registered users from localStorage
|
||||||
|
export const getRegisteredUsers = (): string[] => {
|
||||||
|
if (!isBrowser()) return []
|
||||||
|
try {
|
||||||
|
return JSON.parse(window.localStorage.getItem('registeredUsers') || '[]')
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error getting registered users:', error)
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a user to the registered users list
|
||||||
|
export const addRegisteredUser = (username: string): void => {
|
||||||
|
if (!isBrowser()) return
|
||||||
|
try {
|
||||||
|
const users = getRegisteredUsers()
|
||||||
|
if (!users.includes(username)) {
|
||||||
|
users.push(username)
|
||||||
|
window.localStorage.setItem('registeredUsers', JSON.stringify(users))
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error adding registered user:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if a username is available
|
||||||
|
export const isUsernameAvailable = (username: string): boolean => {
|
||||||
|
if (!isBrowser()) return true
|
||||||
|
const users = getRegisteredUsers()
|
||||||
|
return !users.includes(username)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store a public key for a user
|
||||||
|
export const storePublicKey = (username: string, publicKey: string): void => {
|
||||||
|
if (!isBrowser()) return
|
||||||
|
try {
|
||||||
|
window.localStorage.setItem(`${username}_publicKey`, publicKey)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error storing public key:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate a key pair using Web Crypto API
|
||||||
|
export const generateKeyPair = async (): Promise<CryptoKeyPair | null> => {
|
||||||
|
if (!isBrowser()) return null
|
||||||
|
try {
|
||||||
|
return await window.crypto.subtle.generateKey(
|
||||||
|
{
|
||||||
|
name: 'ECDSA',
|
||||||
|
namedCurve: 'P-256',
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
['sign', 'verify']
|
||||||
|
)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error generating key pair:', error)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Export a public key to a base64 string
|
||||||
|
export const exportPublicKey = async (publicKey: CryptoKey): Promise<string | null> => {
|
||||||
|
if (!isBrowser()) return null
|
||||||
|
try {
|
||||||
|
const publicKeyBuffer = await window.crypto.subtle.exportKey(
|
||||||
|
'raw',
|
||||||
|
publicKey
|
||||||
|
)
|
||||||
|
|
||||||
|
return btoa(
|
||||||
|
String.fromCharCode.apply(null, Array.from(new Uint8Array(publicKeyBuffer)))
|
||||||
|
)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error exporting public key:', error)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,27 +4,35 @@ import { setup } from 'webnative'
|
||||||
import { filesystemStore, sessionStore } from '../stores'
|
import { filesystemStore, sessionStore } from '../stores'
|
||||||
import { getBackupStatus, type BackupStatus } from '$lib/auth/backup'
|
import { getBackupStatus, type BackupStatus } from '$lib/auth/backup'
|
||||||
|
|
||||||
// TODO: Add a flag or script to turn debugging on/off
|
// Enable debugging to see detailed logs
|
||||||
setup.debug({ enabled: false })
|
setup.debug({ enabled: true })
|
||||||
|
|
||||||
export const initialize = async (): Promise<void> => {
|
export const initialize = async (): Promise<void> => {
|
||||||
|
console.log('Initializing webnative app...')
|
||||||
try {
|
try {
|
||||||
let backupStatus: BackupStatus = null
|
let backupStatus: BackupStatus = null
|
||||||
|
|
||||||
|
console.log('Calling webnative.app with useWnfs: true...')
|
||||||
const state: webnative.AppState = await webnative.app({ useWnfs: true })
|
const state: webnative.AppState = await webnative.app({ useWnfs: true })
|
||||||
|
console.log('Webnative app state:', state.scenario)
|
||||||
|
|
||||||
switch (state.scenario) {
|
switch (state.scenario) {
|
||||||
case webnative.AppScenario.NotAuthed:
|
case webnative.AppScenario.NotAuthed:
|
||||||
|
console.log('User is not authenticated')
|
||||||
sessionStore.set({
|
sessionStore.set({
|
||||||
username: '',
|
username: '',
|
||||||
authed: false,
|
authed: false,
|
||||||
loading: false,
|
loading: false,
|
||||||
backupCreated: null
|
backupCreated: null
|
||||||
})
|
})
|
||||||
|
console.log('Session store updated for unauthenticated user')
|
||||||
break
|
break
|
||||||
|
|
||||||
case webnative.AppScenario.Authed:
|
case webnative.AppScenario.Authed:
|
||||||
|
console.log('User is authenticated with username:', state.username)
|
||||||
|
console.log('Getting backup status...')
|
||||||
backupStatus = await getBackupStatus(state.fs)
|
backupStatus = await getBackupStatus(state.fs)
|
||||||
|
console.log('Backup status:', backupStatus)
|
||||||
|
|
||||||
sessionStore.set({
|
sessionStore.set({
|
||||||
username: state.username,
|
username: state.username,
|
||||||
|
|
@ -32,30 +40,40 @@ export const initialize = async (): Promise<void> => {
|
||||||
loading: false,
|
loading: false,
|
||||||
backupCreated: backupStatus.created
|
backupCreated: backupStatus.created
|
||||||
})
|
})
|
||||||
|
console.log('Session store updated for authenticated user')
|
||||||
|
|
||||||
filesystemStore.set(state.fs)
|
filesystemStore.set(state.fs)
|
||||||
|
console.log('Filesystem store updated')
|
||||||
break
|
break
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
console.log('Unknown scenario:', state.scenario)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
switch (error) {
|
console.error('Error during initialization:', error)
|
||||||
case webnative.InitialisationError.InsecureContext:
|
|
||||||
sessionStore.update(session => ({
|
|
||||||
...session,
|
|
||||||
loading: false,
|
|
||||||
error: 'Insecure Context'
|
|
||||||
}))
|
|
||||||
break
|
|
||||||
|
|
||||||
case webnative.InitialisationError.UnsupportedBrowser:
|
if (error === webnative.InitialisationError.InsecureContext) {
|
||||||
sessionStore.update(session => ({
|
console.error('Initialization error: Insecure Context')
|
||||||
...session,
|
sessionStore.update(session => ({
|
||||||
loading: false,
|
...session,
|
||||||
error: 'Unsupported Browser'
|
loading: false,
|
||||||
}))
|
error: 'Insecure Context'
|
||||||
break
|
}))
|
||||||
|
} else if (error === webnative.InitialisationError.UnsupportedBrowser) {
|
||||||
|
console.error('Initialization error: Unsupported Browser')
|
||||||
|
sessionStore.update(session => ({
|
||||||
|
...session,
|
||||||
|
loading: false,
|
||||||
|
error: 'Unsupported Browser'
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
console.error('Unhandled initialization error:', error)
|
||||||
|
sessionStore.update(session => ({
|
||||||
|
...session,
|
||||||
|
loading: false,
|
||||||
|
error: 'Unsupported Browser' // Using a supported error type
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue