import * as uint8arrays from 'uint8arrays';
import type FileSystem from '@oddjs/odd/fs/index';
import { sha256 } from '@oddjs/odd/components/crypto/implementation/browser';
import { publicKeyToDid } from '@oddjs/odd/did/transformers';
import type { Crypto } from '@oddjs/odd';
import { getRecoil, setRecoil } from 'recoil-nexus';
import { filesystemStore, sessionStore } from '../../stores';
import { AREAS } from '../../routes/playlists/stores';
import { COLLECTION_DIRS } from '../../routes/playlists/lib/playlists';
import { ACCOUNT_SETTINGS_DIR } from '../account-settings';
import { asyncDebounce } from '../utils';
import { getBackupStatus } from './backup';
export const USERNAME_STORAGE_KEY = 'fullUsername';
export enum RECOVERY_STATES {
Ready = 0,
Processing = 1,
Error = 2,
Done = 3,
}
export const isUsernameValid = async (username: string): Promise<boolean> => {
const session = getRecoil(sessionStore);
return session.authStrategy.isUsernameValid(username);
};
export const isUsernameAvailable = async (
username: string
): Promise<boolean> => {
const session = getRecoil(sessionStore);
return session.authStrategy.isUsernameAvailable(username);
};
export const debouncedIsUsernameAvailable = asyncDebounce(
isUsernameAvailable,
300
);
/**
* Create additional directories and files needed by the app
*
* @param fs FileSystem
*/
const initializeFilesystem = async (fs: FileSystem): Promise<void> => {
await fs.mkdir(COLLECTION_DIRS[AREAS.PUBLIC]);
await fs.mkdir(COLLECTION_DIRS[AREAS.PRIVATE]);
await fs.mkdir(ACCOUNT_SETTINGS_DIR);
};
export const createDID = async (
crypto: Crypto.Implementation
): Promise<string> => {
const pubKey = await crypto.keystore.publicExchangeKey();
const ksAlg = await crypto.keystore.getAlgorithm();
return publicKeyToDid(crypto, pubKey, ksAlg);
};
export const prepareUsername = async (username: string): Promise<string> => {
const normalizedUsername = username.normalize('NFD');
const hashedUsername = await sha256(
new TextEncoder().encode(normalizedUsername)
);
return uint8arrays.toString(hashedUsername, 'base32').slice(0, 32);
};
export const register = async (hashedUsername: string): Promise<boolean> => {
const originalSession = getRecoil(sessionStore);
const {
authStrategy,
program: {
components: { storage },
},
} = originalSession;
const { success } = await authStrategy.register({ username: hashedUsername });
if (!success) return success;
const session = await authStrategy.session();
setRecoil(filesystemStore, session.fs);
// TODO Remove if only public and private directories are needed
await initializeFilesystem(session.fs);
const fullUsername = (await storage.getItem(USERNAME_STORAGE_KEY)) as string;
setRecoil(sessionStore, {
...originalSession,
username: {
full: fullUsername,
hashed: hashedUsername,
trimmed: fullUsername.split('#')[0],
},
session,
});
return success;
};
export const loadAccount = async (
hashedUsername: string,
fullUsername: string
): Promise<void> => {
const originalSession = getRecoil(sessionStore);
const {
authStrategy,
program: {
components: { storage },
},
} = originalSession;
const session = await authStrategy.session();
setRecoil(filesystemStore, session.fs);
const backupStatus = await getBackupStatus(session.fs);
await storage.setItem(USERNAME_STORAGE_KEY, fullUsername);
setRecoil(sessionStore, {
...originalSession,
username: {
full: fullUsername,
hashed: hashedUsername,
trimmed: fullUsername.split('#')[0],
},
session,
backupCreated: !!backupStatus?.created,
});
};