diff --git a/components/views/chat/chatbar/Chatbar.html b/components/views/chat/chatbar/Chatbar.html index 5f9a75046d..402b778480 100644 --- a/components/views/chat/chatbar/Chatbar.html +++ b/components/views/chat/chatbar/Chatbar.html @@ -13,6 +13,7 @@ >
{ @@ -156,7 +156,7 @@ export default Vue.extend({ await this.$FileSystem.removeFile(this.item.name) } this.$FileSystem.removeChild(this.item.name) - await this.$Bucket.updateIndex(this.$FileSystem.export) + await this.$TextileManager.bucket?.updateIndex(this.$FileSystem.export) this.$store.commit('ui/setIsLoadingFileIndex', false) this.$emit('forceRender') }, diff --git a/components/views/files/view/View.vue b/components/views/files/view/View.vue index 308c2c5b3b..dbd868af4a 100644 --- a/components/views/files/view/View.vue +++ b/components/views/files/view/View.vue @@ -47,7 +47,7 @@ export default Vue.extend({ if (!this.file.shared) { this.$store.commit('ui/setIsLoadingFileIndex', true) this.file.shareItem() - await this.$Bucket.updateIndex(this.$FileSystem.export) + await this.$TextileManager.bucket?.updateIndex(this.$FileSystem.export) this.$store.commit('ui/setIsLoadingFileIndex', false) } navigator.clipboard.writeText(this.path).then(() => { diff --git a/components/views/friends/add/Add.vue b/components/views/friends/add/Add.vue index 0b1661afbd..11cfe8b328 100644 --- a/components/views/friends/add/Add.vue +++ b/components/views/friends/add/Add.vue @@ -13,6 +13,7 @@ import { debounce } from 'lodash' import { Friend } from '~/types/ui/friends' import ServerProgram from '~/libraries/Solana/ServerProgram/ServerProgram' import SolanaManager from '~/libraries/Solana/SolanaManager/SolanaManager' +import UsersProgram from '~/libraries/Solana/UsersProgram/UsersProgram' export default Vue.extend({ components: { @@ -69,9 +70,9 @@ export default Vue.extend({ this.error = '' try { const $SolanaManager: SolanaManager = Vue.prototype.$SolanaManager - const serverProgram: ServerProgram = new ServerProgram($SolanaManager) + const usersProgram: UsersProgram = new UsersProgram($SolanaManager) - const friend = await serverProgram.getUser(new PublicKey(accountID)) + const friend = await usersProgram.getUserInfo(accountID) if (!friend) { this.error = this.$t('friends.not_found') as string return diff --git a/config.ts b/config.ts index 842500c170..91807de5db 100644 --- a/config.ts +++ b/config.ts @@ -9,6 +9,8 @@ export const Config = { browser: 'https://hub.textile.io', groupChatThreadID: 'bafkv7ordeargenxdutqdltvlo6sbfcfdhuvmocrt4qe6kpohrdbrbdi', + fsTable: 'sat.json', + bucketName: 'personal-files', }, ipfs: { gateway: 'https://satellite.mypinata.cloud/ipfs/', @@ -50,6 +52,7 @@ export const Config = { groupchatsProgramId: 'bJhvwTYCkQceANgeShZ4xaxUqEBPsV8e1NgRnLRymxs', defaultCommitment: 'confirmed' as Commitment, defaultPreflightCommitment: 'confirmed' as Commitment, + usersProgramId: '7MaC2xrAmmFsuRBEkD6BEL3eJpXCmaikYhLM3eKBPhAH', }, // Realms are just different chains we support realms: [ diff --git a/libraries/Files/TextileFileSystem.ts b/libraries/Files/TextileFileSystem.ts index 90796d41ed..72b26cb276 100644 --- a/libraries/Files/TextileFileSystem.ts +++ b/libraries/Files/TextileFileSystem.ts @@ -9,7 +9,7 @@ export class TextileFileSystem extends FilSystem { * @returns {Bucket} bucket global to upload files to textile */ get bucket() { - return Vue.prototype.$Bucket + return Vue.prototype.$TextileManager.bucket } /** diff --git a/libraries/Files/remote/textile/Bucket.ts b/libraries/Files/remote/textile/Bucket.ts index 462907d94d..4c0a8f288e 100644 --- a/libraries/Files/remote/textile/Bucket.ts +++ b/libraries/Files/remote/textile/Bucket.ts @@ -7,29 +7,22 @@ import { } from '@textile/hub' import { RFM } from '../abstracts/RFM.abstract' import { RFMInterface } from '../interface/RFM.interface' -import { TextileErrors } from '../../errors/Errors' import { Config } from '~/config' -import { - BucketConfig, - TextileInitializationData, -} from '~/types/textile/manager' -import IdentityManager from '~/libraries/Textile/IdentityManager' +import { TextileInitializationData } from '~/types/textile/manager' import { FileSystemExport, FILESYSTEM_TYPE, } from '~/libraries/Files/types/filesystem' export class Bucket extends RFM implements RFMInterface { - private creds: { id: any; pass: any } = { id: null, pass: null } - private identityManager: IdentityManager - private _textile: TextileInitializationData | null = null + private _textile: TextileInitializationData private _index: FileSystemExport | null = null private buckets: Buckets | null private key: Root['key'] | null - constructor() { + constructor(textile: TextileInitializationData) { super() - this.identityManager = new IdentityManager() + this._textile = textile this.buckets = null this.key = null } @@ -56,58 +49,42 @@ export class Bucket extends RFM implements RFMInterface { * @param param0 Bucket Configuration that includes id, password, SolanaWallet instance, and bucket name * @returns a promise that resolves when the initialization completes */ - async init({ - id, - pass, - wallet, - name, - }: BucketConfig): Promise { - if (!wallet) { - throw new Error(TextileErrors.MISSING_WALLET) - } - - const identity = await this.identityManager.initFromWallet(wallet) - const { client, users } = await this.identityManager.authorize(identity) - - this.creds = { - id, - pass, - } - - this._textile = { - identity, - client, - wallet, - users, - } - + async init(name: string): Promise { if (!Config.textile.key) { throw new Error('Textile key not found') } + this.buckets = await Buckets.withKeyInfo({ key: Config.textile.key }) - await this.buckets.getToken(identity) + await this.buckets.getToken(this._textile.identity) + const result = await this.buckets.getOrCreate(name) + if (!result.root) throw new Error(`failed to open bucket ${name}`) + this.key = result.root.key - const hash = ((await this.buckets.listPath(this.key, 'sat.json')) as Path) - ?.item?.path - - this._index = await fetch(Config.textile.browser + hash) - .then((res) => { - return res.json() - }) - .then((data) => { - return data - }) - .catch(() => { - return { - type: FILESYSTEM_TYPE.DEFAULT, - version: 1, - content: [], - } - }) - return this._index + try { + const path: Path | void = await this.buckets.listPath( + this.key, + Config.textile.fsTable, + ) + + const hash = path?.item?.path + + this._index = await fetch(Config.textile.browser + hash).then((res) => + res.json(), + ) + + if (!this._index) throw new Error('Index not found') + + return this._index + } catch (e) { + return { + type: FILESYSTEM_TYPE.DEFAULT, + version: 1, + content: [], + } + } } /** @@ -122,7 +99,7 @@ export class Bucket extends RFM implements RFMInterface { this._index = index await this.buckets.pushPath( this.key, - 'sat.json', + Config.textile.fsTable, Buffer.from(JSON.stringify(index)), ) } diff --git a/libraries/Files/test/Bucket.test.ts b/libraries/Files/test/Bucket.test.ts index 2dc53695bd..c6a04634c7 100644 --- a/libraries/Files/test/Bucket.test.ts +++ b/libraries/Files/test/Bucket.test.ts @@ -1,6 +1,5 @@ import { Fil } from '../Fil' import { FilSystem } from '../FilSystem' -import { Bucket } from '../remote/textile/Bucket' import { DIRECTORY_TYPE } from '../types/directory' import { FileSystemExport } from '../types/filesystem' @@ -34,9 +33,5 @@ describe('Test FileSystem Directory', () => { expect(ex.version + 1).toEqual(fs.export.version) }) - it('get uninitialized textile', () => { - const bucket = new Bucket() - - expect(bucket.textile).toBeNull() - }) + // TODO: add test for Bucket }) diff --git a/libraries/Solana/FriendsProgram/FriendsProgram.ts b/libraries/Solana/FriendsProgram/FriendsProgram.ts index 9f0c866b6c..657a903bb8 100644 --- a/libraries/Solana/FriendsProgram/FriendsProgram.ts +++ b/libraries/Solana/FriendsProgram/FriendsProgram.ts @@ -637,17 +637,17 @@ export default class FriendsProgram extends EventEmitter { const { connection } = this.solana - const userAccount = this.solana.getUserAccount() - if (!userAccount) { + const payerAccount = this.solana.getActiveAccount() + if (!payerAccount) { throw new Error('User account not found') } const fromKeyAndStatus = base58( - Buffer.from([...userAccount.publicKey.toBytes(), status]), + Buffer.from([...payerAccount.publicKey.toBytes(), status]), ) const statusAndToKey = base58( - Buffer.from([status, ...userAccount.publicKey.toBytes()]), + Buffer.from([status, ...payerAccount.publicKey.toBytes()]), ) const outgoing = await connection.getProgramAccounts(FRIENDS_PROGRAM_ID, { @@ -693,8 +693,8 @@ export default class FriendsProgram extends EventEmitter { } const { connection } = this.solana - const userAccount = this.solana.getUserAccount() - if (!userAccount) { + const payerAccount = this.solana.getActiveAccount() + if (!payerAccount) { throw new Error('User account not found') } @@ -703,7 +703,7 @@ export default class FriendsProgram extends EventEmitter { // formatted that way // [32 bytes (sender public key)][1 byte (status)][32 bytes (recipient public key)] const incomingRequestBytes = base58( - Buffer.from([FriendStatus.PENDING, ...userAccount.publicKey.toBytes()]), + Buffer.from([FriendStatus.PENDING, ...payerAccount.publicKey.toBytes()]), ) const incomingRequestsFilter: GetProgramAccountsFilter = { @@ -723,7 +723,7 @@ export default class FriendsProgram extends EventEmitter { // This filter checks the sender public key (our) and the status // [32 bytes (sender public key)][1 byte (status)][32 bytes (recipient public key)] const newFriendFromOutgoingBytes = base58( - Buffer.from([...userAccount.publicKey.toBytes(), FriendStatus.ACCEPTED]), + Buffer.from([...payerAccount.publicKey.toBytes(), FriendStatus.ACCEPTED]), ) const newFriendFromOutgoingFilter: GetProgramAccountsFilter = { @@ -741,7 +741,7 @@ export default class FriendsProgram extends EventEmitter { // This filter checks the sender public key (our) and the status // [32 bytes (sender public key)][1 byte (status)][32 bytes (recipient public key)] const friendRequestDeniedBytes = base58( - Buffer.from([...userAccount.publicKey.toBytes(), FriendStatus.REFUSED]), + Buffer.from([...payerAccount.publicKey.toBytes(), FriendStatus.REFUSED]), ) const friendRequestDeniedFilter: GetProgramAccountsFilter = { @@ -761,7 +761,7 @@ export default class FriendsProgram extends EventEmitter { // This filter checks the sender public key (our) and the status // [32 bytes (sender public key)][1 byte (status)][32 bytes (recipient public key)] const friendRequestRemovedBytes = base58( - Buffer.from([...userAccount.publicKey.toBytes(), FriendStatus.REMOVED]), + Buffer.from([...payerAccount.publicKey.toBytes(), FriendStatus.REMOVED]), ) const friendRequestRemovedFilter: GetProgramAccountsFilter = { @@ -776,7 +776,7 @@ export default class FriendsProgram extends EventEmitter { ) const friendRequestRemovedMirroredBytes = base58( - Buffer.from([FriendStatus.REMOVED, ...userAccount.publicKey.toBytes()]), + Buffer.from([FriendStatus.REMOVED, ...payerAccount.publicKey.toBytes()]), ) const friendRequestRemovedMirroredFilter: GetProgramAccountsFilter = { diff --git a/libraries/Solana/UsersProgram/UsersProgram.ts b/libraries/Solana/UsersProgram/UsersProgram.ts new file mode 100644 index 0000000000..98bb341cf7 --- /dev/null +++ b/libraries/Solana/UsersProgram/UsersProgram.ts @@ -0,0 +1,236 @@ +import { EventEmitter } from 'events' +import { Program, Wallet, web3, utils, Provider } from '@project-serum/anchor' +import { Users, IDL } from './UsersProgram.types' + +import Solana from '~/libraries/Solana/SolanaManager/SolanaManager' +import { Config } from '~/config' + +const { PublicKey, SystemProgram } = web3 + +export const USERS_PROGRAM_ID = new PublicKey(Config.solana.usersProgramId) + +const userSeed = Buffer.from(utils.bytes.utf8.encode('user')) + +export default class UsersProgram extends EventEmitter { + solana?: Solana + program?: Program + subscriptions?: { [eventName: string]: number } + + constructor(solana: Solana) { + super() + if (solana) { + this.init(solana) + } + } + + /** + * @method init + * Initializes the class with the SolanaManager instance + * @param solana SolanaManager instance + */ + init(solana: Solana) { + this.solana = solana + + const payer = this._getPayer() + + const provider = new Provider(this.solana.connection, new Wallet(payer), { + commitment: Config.solana.defaultCommitment, + preflightCommitment: Config.solana.defaultPreflightCommitment, + }) + + this.program = new Program( + IDL, + USERS_PROGRAM_ID.toBase58(), + provider, + ) + } + + /** + * @method _userPDAPublicKey + * Computes a user PDA for the given Public Key + * @param address String version of the Public Key + * @returns the computed PDA + */ + protected async _userPDAPublicKey(address: string) { + const program = this._getProgram() + + const user = new PublicKey(address) + + return utils.publicKey.findProgramAddressSync( + [user.toBytes(), userSeed], + program.programId, + ) + } + + /** + * @method _getProgram + * Returns the anchor program instance for group chat + * @returns the anchor program instance + */ + protected _getProgram() { + if (!this.program) { + throw new Error('Group Chat Manager not initialized') + } + + return this.program + } + + /** + * @method _getPayer + * Retrieve the active account from Solana wallet + * @returns the payer account + */ + protected _getPayer() { + const payer = this.solana?.getActiveAccount() + + if (!payer) { + throw new Error('Missing payer') + } + + return payer + } + + /** + * @method _getUpdateOpts + * Get multiple information useful for the update functions + * @returns program instance, payer account, user info and user PDA + */ + protected async _getUpdateOpts() { + const program = this._getProgram() + const payer = this._getPayer() + + const userPDA = await this._userPDAPublicKey(payer.publicKey.toBase58()) + + const userInfo = await this.getCurrentUserInfo() + + if (!userInfo) { + throw new Error('Non existent account') + } + + return { program, payer, userPDA, userInfo } + } + + /** + * @method create + * Create a new user + * @param name Username + * @param photoHash Profile picture IPFS hash + * @param statusMessage Status message string + */ + async create(name: string, photoHash: string, statusMessage: string) { + // Throws if the program is not set + const program = this._getProgram() + + // Throws if the payer is not set + const payer = this._getPayer() + + const userPDA = utils.publicKey.findProgramAddressSync( + [payer.publicKey.toBytes(), userSeed], + program.programId, + ) + + await program.rpc.create(name, photoHash, statusMessage, { + accounts: { + user: userPDA[0], + signer: payer.publicKey, + payer: payer.publicKey, + systemProgram: SystemProgram.programId, + }, + signers: [payer], + }) + } + + /** + * @method getUserInfo + * Fetch the user information from the user program + * @param address string representation of the Public key + * @returns the parsed user info + */ + async getUserInfo(address: string) { + const program = this._getProgram() + + const userPDA = await this._userPDAPublicKey(address) + + const userInfo = await program.account.user.fetchNullable(userPDA[0]) + + if (!userInfo) { + return null + } + + return { + name: userInfo.name as string, + photoHash: userInfo.photoHash as string, + status: userInfo.status as string, + } + } + + /** + * @method getCurrentUserInfo + * Returns the user info for the active account + * @returns the user info object for the current + */ + async getCurrentUserInfo() { + const payer = this._getPayer() + + return this.getUserInfo(payer.publicKey.toBase58()) + } + + /** + * @method setName + * Allow the user to update the user name + * @param name user name + * @returns the transaction to update the status message + */ + async setName(name: string) { + const { program, userPDA, payer } = await this._getUpdateOpts() + + return program.rpc.setName(name, { + accounts: { + user: userPDA[0], + signer: payer.publicKey, + payer: payer.publicKey, + }, + signers: [payer], + }) + } + + /** + * @method setPhotoHash + * Allow the user to update the profile picture + * @param photoHash profile picture IPFS hash + * @returns the transaction to update the photo hash + */ + async setPhotoHash(photoHash: string) { + const { program, userPDA, payer } = await this._getUpdateOpts() + + return program.rpc.setPhotoHash(photoHash, { + accounts: { + user: userPDA[0], + signer: payer.publicKey, + payer: payer.publicKey, + }, + signers: [payer], + }) + } + + /** + * @method setStatusMessage + * Allow the user to update the status message + * @param statusMessage status message + * @returns the transaction to update the status message + */ + async setStatusMessage(statusMessage: string) { + const { program, userPDA, payer } = await this._getUpdateOpts() + + return program.rpc.setStatus(statusMessage, { + accounts: { + user: userPDA[0], + signer: payer.publicKey, + payer: payer.publicKey, + }, + signers: [payer], + }) + } +} + +export type UserInfo = Awaited> diff --git a/libraries/Solana/UsersProgram/UsersProgram.types.ts b/libraries/Solana/UsersProgram/UsersProgram.types.ts new file mode 100644 index 0000000000..cf4758337f --- /dev/null +++ b/libraries/Solana/UsersProgram/UsersProgram.types.ts @@ -0,0 +1,315 @@ +export type Users = { + version: '0.1.0' + name: 'users' + instructions: [ + { + name: 'create' + accounts: [ + { + name: 'user' + isMut: true + isSigner: false + }, + { + name: 'signer' + isMut: false + isSigner: true + }, + { + name: 'payer' + isMut: true + isSigner: true + }, + { + name: 'systemProgram' + isMut: false + isSigner: false + }, + ] + args: [ + { + name: 'name' + type: 'string' + }, + { + name: 'photoHash' + type: 'string' + }, + { + name: 'status' + type: 'string' + }, + ] + }, + { + name: 'setName' + accounts: [ + { + name: 'user' + isMut: true + isSigner: false + }, + { + name: 'signer' + isMut: false + isSigner: true + }, + { + name: 'payer' + isMut: true + isSigner: true + }, + ] + args: [ + { + name: 'name' + type: 'string' + }, + ] + }, + { + name: 'setPhotoHash' + accounts: [ + { + name: 'user' + isMut: true + isSigner: false + }, + { + name: 'signer' + isMut: false + isSigner: true + }, + { + name: 'payer' + isMut: true + isSigner: true + }, + ] + args: [ + { + name: 'photoHash' + type: 'string' + }, + ] + }, + { + name: 'setStatus' + accounts: [ + { + name: 'user' + isMut: true + isSigner: false + }, + { + name: 'signer' + isMut: false + isSigner: true + }, + { + name: 'payer' + isMut: true + isSigner: true + }, + ] + args: [ + { + name: 'status' + type: 'string' + }, + ] + }, + ] + accounts: [ + { + name: 'user' + type: { + kind: 'struct' + fields: [ + { + name: 'name' + type: 'string' + }, + { + name: 'photoHash' + type: 'string' + }, + { + name: 'status' + type: 'string' + }, + ] + } + }, + ] + errors: [ + { + code: 6000 + name: 'WrongPrivileges' + msg: 'User cannot perform this action' + }, + { + code: 6001 + name: 'PayerMismatch' + msg: 'Account was not created by provided user' + }, + ] +} + +export const IDL: Users = { + version: '0.1.0', + name: 'users', + instructions: [ + { + name: 'create', + accounts: [ + { + name: 'user', + isMut: true, + isSigner: false, + }, + { + name: 'signer', + isMut: false, + isSigner: true, + }, + { + name: 'payer', + isMut: true, + isSigner: true, + }, + { + name: 'systemProgram', + isMut: false, + isSigner: false, + }, + ], + args: [ + { + name: 'name', + type: 'string', + }, + { + name: 'photoHash', + type: 'string', + }, + { + name: 'status', + type: 'string', + }, + ], + }, + { + name: 'setName', + accounts: [ + { + name: 'user', + isMut: true, + isSigner: false, + }, + { + name: 'signer', + isMut: false, + isSigner: true, + }, + { + name: 'payer', + isMut: true, + isSigner: true, + }, + ], + args: [ + { + name: 'name', + type: 'string', + }, + ], + }, + { + name: 'setPhotoHash', + accounts: [ + { + name: 'user', + isMut: true, + isSigner: false, + }, + { + name: 'signer', + isMut: false, + isSigner: true, + }, + { + name: 'payer', + isMut: true, + isSigner: true, + }, + ], + args: [ + { + name: 'photoHash', + type: 'string', + }, + ], + }, + { + name: 'setStatus', + accounts: [ + { + name: 'user', + isMut: true, + isSigner: false, + }, + { + name: 'signer', + isMut: false, + isSigner: true, + }, + { + name: 'payer', + isMut: true, + isSigner: true, + }, + ], + args: [ + { + name: 'status', + type: 'string', + }, + ], + }, + ], + accounts: [ + { + name: 'user', + type: { + kind: 'struct', + fields: [ + { + name: 'name', + type: 'string', + }, + { + name: 'photoHash', + type: 'string', + }, + { + name: 'status', + type: 'string', + }, + ], + }, + }, + ], + errors: [ + { + code: 6000, + name: 'WrongPrivileges', + msg: 'User cannot perform this action', + }, + { + code: 6001, + name: 'PayerMismatch', + msg: 'Account was not created by provided user', + }, + ], +} diff --git a/libraries/Textile/TextileManager.ts b/libraries/Textile/TextileManager.ts index eb90957593..0725b5be43 100644 --- a/libraries/Textile/TextileManager.ts +++ b/libraries/Textile/TextileManager.ts @@ -1,3 +1,4 @@ +import { Bucket } from '../Files/remote/textile/Bucket' import IdentityManager from '~/libraries/Textile/IdentityManager' import { MailboxManager } from '~/libraries/Textile/MailboxManager' import { @@ -7,12 +8,14 @@ import { } from '~/types/textile/manager' import BucketManager from '~/libraries/Textile/BucketManager' import { GroupChatManager } from '~/libraries/Textile/GroupChatManager' +import { Config } from '~/config' export default class TextileManager { creds?: Creds identityManager: IdentityManager mailboxManager?: MailboxManager bucketManager?: BucketManager + bucket?: Bucket groupChatManager?: GroupChatManager constructor() { @@ -70,6 +73,10 @@ export default class TextileManager { ) await this.bucketManager.init().catch((e) => console.log(e)) + // Initialize bucket + this.bucket = new Bucket(textile) + await this.bucket.init(Config.textile.bucketName) + // GroupChatManager initializes itself during the creation this.groupChatManager = new GroupChatManager( textile, diff --git a/middleware/authenticated.ts b/middleware/authenticated.ts index 90aa1c56fc..d06ff2898c 100644 --- a/middleware/authenticated.ts +++ b/middleware/authenticated.ts @@ -45,11 +45,5 @@ export default function ({ store, route, redirect }: Arguments) { return } - const allPrerequisitesReady = - store.getters['prerequisites/allPrerequisitesReady'] - if (!allPrerequisitesReady) { - return eventuallyRedirect('/') - } - store.commit('accounts/setLastVisited', route.path) } diff --git a/pages/auth/register/Register.html b/pages/auth/register/Register.html index a65ee764a6..43d5ac76df 100644 --- a/pages/auth/register/Register.html +++ b/pages/auth/register/Register.html @@ -2,7 +2,7 @@
diff --git a/pages/auth/register/index.vue b/pages/auth/register/index.vue index bd934d53d3..02156b7e23 100644 --- a/pages/auth/register/index.vue +++ b/pages/auth/register/index.vue @@ -18,6 +18,7 @@ export default Vue.extend({ }, computed: { ...mapGetters('accounts', ['getRegistrationStatus']), + ...mapGetters(['allPrerequisitesReady']), hasToRegister() { return this.getRegistrationStatus === RegistrationStatus.UNKNOWN }, @@ -32,23 +33,27 @@ export default Vue.extend({ 'user.registration.reg_status.sending_transaction', ) default: - return this.$i18n.t('user.registration.reg_status.registered') + return this.$i18n.t('user.loading.loading_account') } }, isRegistered() { return this.getRegistrationStatus === RegistrationStatus.REGISTERED }, }, + watch: { + allPrerequisitesReady(nextValue) { + if (!nextValue) return + this.$router.replace('/chat/direct') + }, + }, mounted() {}, methods: { async confirm(userData: UserRegistrationData) { - await this.$store.dispatch('accounts/registerUser', { + this.$store.dispatch('accounts/registerUser', { name: userData.username, image: userData.photoHash, status: userData.status, }) - - this.$router.replace('/chat/direct') }, }, }) diff --git a/pages/auth/unlock/index.vue b/pages/auth/unlock/index.vue index 78274ccf27..37ff043411 100644 --- a/pages/auth/unlock/index.vue +++ b/pages/auth/unlock/index.vue @@ -47,7 +47,6 @@ export default Vue.extend({ ConsoleWarning(this.$config.clientVersion, this.$store.state) this.$store.commit('accounts/lock') - this.$store.commit('prerequisites/resetState') }, methods: { /** diff --git a/pages/generic/loading/Loading.html b/pages/generic/loading/Loading.html deleted file mode 100644 index e14507b567..0000000000 --- a/pages/generic/loading/Loading.html +++ /dev/null @@ -1,7 +0,0 @@ -
-
- - - -
-
diff --git a/pages/generic/loading/Loading.less b/pages/generic/loading/Loading.less deleted file mode 100644 index 6c4d7c4884..0000000000 --- a/pages/generic/loading/Loading.less +++ /dev/null @@ -1,24 +0,0 @@ -.container { - margin: 0 auto; - display: flex; - justify-content: center; - - .loading-body { - min-width: 250px; - align-self: center; - margin-bottom: 25vh; - - .subtitle { - float: right; - } - } -} - -@media only screen and (max-width: @mobile-breakpoint) { - .container { - .loading-body { - margin: 0; - } - margin: 0; - } -} diff --git a/pages/generic/loading/index.vue b/pages/generic/loading/index.vue deleted file mode 100644 index 3f8cd2acd2..0000000000 --- a/pages/generic/loading/index.vue +++ /dev/null @@ -1,88 +0,0 @@ - - - - - diff --git a/pages/index.vue b/pages/index.vue index 0b6707318c..7ee90aaee0 100644 --- a/pages/index.vue +++ b/pages/index.vue @@ -20,7 +20,7 @@ export default Vue.extend({ }, computed: { ...mapGetters('accounts', ['getEncryptedPhrase', 'getActiveAccount']), - ...mapGetters('prerequisites', ['allPrerequisitesReady']), + ...mapGetters(['allPrerequisitesReady']), ...mapState(['accounts']), // Helper method for prettier loading messages loadingStep(): string { @@ -31,21 +31,9 @@ export default Vue.extend({ }, }, watch: { - // this.$router.replace('/chat/direct') allPrerequisitesReady(nextValue) { - if (nextValue) { - if ( - this.accounts.lastVisited && - this.accounts.lastVisited !== this.$route.path - ) { - const matcher = this.$router.match(this.accounts.lastVisited) - if (matcher.matched.length > 0) { - this.$router.replace(this.accounts.lastVisited) - return - } - } - this.$router.replace('/chat/direct') - } + if (!nextValue) return + this.eventuallyRedirect() }, }, mounted() { @@ -54,9 +42,21 @@ export default Vue.extend({ this.$router.replace('/setup/disclaimer') return } + this.loadAccount() }, methods: { + eventuallyRedirect() { + if (this.accounts.lastVisited === this.$route.path) { + this.$router.replace('/chat/direct') + return + } + + const matcher = this.$router.match(this.accounts.lastVisited) + if (matcher.matched.length > 0) { + this.$router.replace(this.accounts.lastVisited) + } + }, /** * @method loadAccount * @description Load user account by dispatching the loadAccount action in store/accounts/actions.ts, diff --git a/plugins/local/classLoader.ts b/plugins/local/classLoader.ts index 9457719c04..a8b084d0a5 100644 --- a/plugins/local/classLoader.ts +++ b/plugins/local/classLoader.ts @@ -33,7 +33,6 @@ declare module 'vue/types/vue' { $Hounddog: Hounddog $Logger: Logger $Alerts: Alerts - $Bucket: Bucket $FileSystem: TextileFileSystem } } @@ -52,7 +51,6 @@ declare module '@nuxt/types' { $Hounddog: Hounddog $Logger: Logger $Alerts: Alerts - $Bucket: Bucket $FileSystem: TextileFileSystem } } @@ -67,7 +65,6 @@ Vue.prototype.$Config = Config Vue.prototype.$Hounddog = new Hounddog(Vue.prototype.$store) Vue.prototype.$Logger = new Logger(Vue.prototype.$Config.debug) Vue.prototype.$Alerts = new Alerts() -Vue.prototype.$Bucket = new Bucket() Vue.prototype.$FileSystem = new TextileFileSystem() // Add typed store alias to Vue prototype diff --git a/plugins/thirdparty/persist.ts b/plugins/thirdparty/persist.ts index 95ccbb43da..92a58900f6 100644 --- a/plugins/thirdparty/persist.ts +++ b/plugins/thirdparty/persist.ts @@ -19,8 +19,10 @@ const mutationsBlacklist = [ // State properties path to blacklist saving to store const commonProperties = [ + 'webrtc.initialized', + 'textile.initialized', + 'accounts.initialized', 'friends.all', - 'prerequisites', 'webrtc.activeStream', 'webrtc.connectedPeer', 'webrtc.incomingCall', diff --git a/store/accounts/__snapshots__/state.test.ts.snap b/store/accounts/__snapshots__/state.test.ts.snap index ca3e1573cf..443e117e60 100644 --- a/store/accounts/__snapshots__/state.test.ts.snap +++ b/store/accounts/__snapshots__/state.test.ts.snap @@ -6,7 +6,8 @@ Object { "encryptedPhrase": "", "error": "", "gasPrice": "", - "lastVisited": "", + "initialized": false, + "lastVisited": "/", "loading": false, "locked": true, "phrase": "", diff --git a/store/accounts/actions.ts b/store/accounts/actions.ts index 4d8916d79f..e91d5e65d2 100644 --- a/store/accounts/actions.ts +++ b/store/accounts/actions.ts @@ -7,8 +7,8 @@ import { UserRegistrationPayload, } from './types' import Crypto from '~/libraries/Crypto/Crypto' -import ServerProgram from '~/libraries/Solana/ServerProgram/ServerProgram' import SolanaManager from '~/libraries/Solana/SolanaManager/SolanaManager' +import UsersProgram from '~/libraries/Solana/UsersProgram/UsersProgram' import { ActionsArguments, RootState } from '~/types/store/store' import TextileManager from '~/libraries/Textile/TextileManager' @@ -18,10 +18,14 @@ import { FilSystem } from '~/libraries/Files/FilSystem' export default { /** - * @method setPin DocsTODO - * @description - * @param pin + * @method setPin + * @description sets the user pin password and stores its + * hash inside the Vuex state + * @param pin the choosen pin password * @example + * ```typescript + * this.$store.dispatch('accounts/setPin', 'myPassword123') + * ``` */ async setPin({ commit }: ActionsArguments, pin: string) { if (pin.length < 5) { @@ -32,16 +36,19 @@ export default { const pinHash = await $Crypto.hash(pin) - // The cleartext version of the pin will not be - // persisted + // The cleartext version of the pin will not be persisted commit('setPin', pin) commit('setPinHash', pinHash) }, /** - * @method unlock DocsTODO - * @description - * @param pin + * @method unlock + * @description performs all the actions to unlock the app by + * decrypting the wallet information + * @param pin pin password in use * @example + * ```typescript + * this.$store.dispatch('accounts/unlock', 'myPassword123') + * ``` */ async unlock( { commit, state }: ActionsArguments, @@ -73,10 +80,12 @@ export default { commit('unlock', pin) }, /** - * @method generateWallet DocsTODO - * @description - * @param + * @method generateWallet + * @description Generates a new Solana hierarchical wallet * @example + * ```typescript + * this.$store.dispatch('accounts/generateWallet') + * ``` */ async generateWallet({ commit, state }: ActionsArguments) { const { pin } = state @@ -104,10 +113,14 @@ export default { commit('setEncryptedPhrase', encryptedPhrase) }, /** - * @method setRecoverMnemonic DocsTODO - * @description - * @param mnemonic + * @method setRecoverMnemonic + * @description Encrypts the wallet mnemonic phrase using the user pin + * password and stores it inside Vuex store + * @param mnemonic the mnemonic phrase to store * @example + * ```typescript + * this.$store.dispatch('accounts/setRecoverMnemonic','my seed phrase') + * ``` */ async setRecoverMnemonic( { commit, state }: ActionsArguments, @@ -125,10 +138,13 @@ export default { await commit('setEncryptedPhrase', encryptedPhrase) }, /** - * @method loadAccount DocsTODO - * @description - * @param + * @method loadAccount + * @description Performs all the action needed to retrieve the user account + * from Solana * @example + * ```typescript + * this.$store.dispatch('accounts/loadAccount') + * ``` */ async loadAccount({ commit, @@ -145,73 +161,46 @@ export default { await $SolanaManager.initializeFromMnemonic(mnemonic) - const userAccount = $SolanaManager.getUserAccount() + const payerAccount = $SolanaManager.getActiveAccount() - if (!userAccount) { + if (!payerAccount) { throw new Error(AccountsError.USER_DERIVATION_FAILED) } - commit('setActiveAccount', userAccount?.publicKey.toBase58()) + commit('setActiveAccount', payerAccount?.publicKey.toBase58()) - const serverProgram: ServerProgram = new ServerProgram($SolanaManager) + const usersProgram: UsersProgram = new UsersProgram($SolanaManager) - const userInfo = await serverProgram.getUser(userAccount.publicKey) + const userInfo = await usersProgram.getCurrentUserInfo() if (userInfo === null) { throw new Error(AccountsError.USER_NOT_REGISTERED) } - // Initialize Encryption Engine - dispatch('initializeEncryptionEngine', userAccount) + dispatch('initializeEncryptionEngine', payerAccount) commit('setUserDetails', { username: userInfo.name, ...userInfo, }) - commit('prerequisites/setAccountsReady', true, { root: true }) - - // TODO: move this logic into a startup action - // Initialize textile - const { pin } = state - dispatch( - 'textile/initialize', - { - id: userAccount?.publicKey.toBase58(), - pass: pin, - wallet: $SolanaManager.getMainSolanaWalletInstance(), - }, - { root: true }, - ) - - // initialize bucket and file system - const $Bucket: Bucket = Vue.prototype.$Bucket - const fsExport = await $Bucket.init({ - id: userAccount?.publicKey.toBase58(), - pass: pin!, - wallet: $SolanaManager.getMainSolanaWalletInstance(), - name: 'personal-files', - }) - if (fsExport) { - const $FileSystem: FilSystem = Vue.prototype.$FileSystem - $FileSystem.import(fsExport) - } - - // Initialize WebRTC with our ID - dispatch('webrtc/initialize', userAccount.publicKey.toBase58(), { - root: true, - }) - - // Dispatch an action to fetch friends and friends requests - dispatch('friends/fetchFriends', {}, { root: true }) - dispatch('friends/fetchFriendRequests', {}, { root: true }) - dispatch('friends/subscribeToFriendsEvents', {}, { root: true }) + dispatch('startup', payerAccount) }, /** - * @method registerUser DocsTODO - * @description - * @param userData + * @method registerUser + * @description Registers a new user on the Solana blockchain + * @param userData User information to register * @example + * ```typescript + * this.$store.dispatch( + * 'accounts/registerUser', + * { + * name: 'My Name', + * image: 'linkToMyImage', + * status: 'My amazing status message 🚀' + * } + * ); + * ``` */ async registerUser( { commit, state, dispatch }: ActionsArguments, @@ -235,16 +224,9 @@ export default { throw new Error(AccountsError.PAYER_NOT_PRESENT) } - const userAccount = await $SolanaManager.getUserAccount() - - if (!userAccount) { - commit('setRegistrationStatus', RegistrationStatus.UNKNOWN) - throw new Error(AccountsError.USER_DERIVATION_FAILED) - } - - const serverProgram: ServerProgram = new ServerProgram($SolanaManager) + const usersProgram: UsersProgram = new UsersProgram($SolanaManager) - const userInfo = await serverProgram.getUser(userAccount.publicKey) + const userInfo = await usersProgram.getCurrentUserInfo() if (userInfo) { commit('setRegistrationStatus', RegistrationStatus.REGISTERED) @@ -259,7 +241,7 @@ export default { await dispatch( 'textile/initialize', { - id: userAccount?.publicKey.toBase58(), + id: payerAccount?.publicKey.toBase58(), pass: pin, wallet: $SolanaManager.getMainSolanaWalletInstance(), }, @@ -269,32 +251,72 @@ export default { const imagePath = await uploadPicture(userData.image) - await serverProgram.createUser(userData.name, imagePath, userData.status) + await usersProgram.create(userData.name, imagePath, userData.status) commit('setRegistrationStatus', RegistrationStatus.REGISTERED) - commit('setActiveAccount', userAccount.publicKey.toBase58()) + commit('setActiveAccount', payerAccount.publicKey.toBase58()) + + dispatch('initializeEncryptionEngine', payerAccount) - // Initialize Encryption Engine - dispatch('initializeEncryptionEngine', userAccount) commit('setUserDetails', { username: userData.name, status: userData.status, photoHash: imagePath, - address: userAccount.publicKey.toBase58(), + address: payerAccount.publicKey.toBase58(), }) + + dispatch('startup', payerAccount) }, /** - * @method initializeEncryptionEngine DocsTODO - * @description - * @param userAccount + * @method initializeEncryptionEngine + * @description Initializes the Crypto class with the current user keypair + * @param userAccount keypair of the current user * @example + * ```typescript + * this.$store.dispatch('accounts/initializeEncriptionEngin', currentUserAccount) + * ``` */ - async initializeEncryptionEngine(_: RootState, userAccount: Keypair) { + async initializeEncryptionEngine( + _: ActionsArguments, + userAccount: Keypair, + ) { // Initialize crypto engine const $Crypto: Crypto = Vue.prototype.$Crypto await $Crypto.init(userAccount) }, + async startup( + { dispatch, rootState, state }: ActionsArguments, + payerAccount: Keypair, + ) { + const $SolanaManager: SolanaManager = Vue.prototype.$SolanaManager + + const { initialized: textileInitialized } = rootState.textile + const { initialized: webrtcInitialized } = rootState.webrtc + + const { pin } = state + if (!textileInitialized && pin) { + dispatch( + 'textile/initialize', + { + id: payerAccount?.publicKey.toBase58(), + pass: pin, + wallet: $SolanaManager.getMainSolanaWalletInstance(), + }, + { root: true }, + ) + } + + if (!webrtcInitialized) { + dispatch('webrtc/initialize', payerAccount.publicKey.toBase58(), { + root: true, + }) + } + + dispatch('friends/fetchFriends', {}, { root: true }) + dispatch('friends/fetchFriendRequests', {}, { root: true }) + dispatch('friends/subscribeToFriendsEvents', {}, { root: true }) + }, } /** diff --git a/store/accounts/mutations.ts b/store/accounts/mutations.ts index 77775c8f53..931dd59437 100644 --- a/store/accounts/mutations.ts +++ b/store/accounts/mutations.ts @@ -29,6 +29,7 @@ const mutations = { }, setActiveAccount(state: AccountsState, activeAccountPubkey: string) { state.active = activeAccountPubkey + state.initialized = true }, setUserDetails(state: AccountsState, details: UserRegistrationData) { state.details = { @@ -37,6 +38,7 @@ const mutations = { profilePicture: details.photoHash, address: state.active, state: 'online', + lastUpdate: Date.now(), } }, updateMailboxId(state: AccountsState, mailboxId: string) { diff --git a/store/accounts/state.ts b/store/accounts/state.ts index 1b73dcc82d..720e474f92 100644 --- a/store/accounts/state.ts +++ b/store/accounts/state.ts @@ -1,6 +1,7 @@ import { AccountsState, RegistrationStatus } from './types' const InitialAccountsState = (): AccountsState => ({ + initialized: false, storePin: false, registry: true, locked: true, @@ -13,7 +14,7 @@ const InitialAccountsState = (): AccountsState => ({ loading: false, registered: false, registrationStatus: RegistrationStatus.UNKNOWN, - lastVisited: '', + lastVisited: '/', }) export default InitialAccountsState diff --git a/store/accounts/types.ts b/store/accounts/types.ts index 72f3db2b5e..ce563588b5 100644 --- a/store/accounts/types.ts +++ b/store/accounts/types.ts @@ -9,6 +9,7 @@ export enum RegistrationStatus { } export interface AccountsState { + initialized: boolean storePin: boolean registry: boolean loading?: boolean diff --git a/store/friends/actions.ts b/store/friends/actions.ts index b97784c8b9..badcec9557 100644 --- a/store/friends/actions.ts +++ b/store/friends/actions.ts @@ -10,7 +10,6 @@ import { import Crypto from '~/libraries/Crypto/Crypto' import SolanaManager from '~/libraries/Solana/SolanaManager/SolanaManager' import FriendsProgram from '~/libraries/Solana/FriendsProgram/FriendsProgram' -import ServerProgram from '~/libraries/Solana/ServerProgram/ServerProgram' import { FriendAccount, FriendsEvents, @@ -24,8 +23,10 @@ import { OutgoingRequest, } from '~/types/ui/friends' import { ActionsArguments } from '~/types/store/store' -import { RawUser } from '~/types/ui/user' import TextileManager from '~/libraries/Textile/TextileManager' +import UsersProgram, { + UserInfo, +} from '~/libraries/Solana/UsersProgram/UsersProgram' export default { /** @@ -39,23 +40,21 @@ export default { const friendsProgram: FriendsProgram = new FriendsProgram($SolanaManager) - const serverProgram: ServerProgram = new ServerProgram($SolanaManager) + const usersProgram: UsersProgram = new UsersProgram($SolanaManager) const { incoming, outgoing } = await friendsProgram.getFriendAccountsByStatus(FriendStatus.PENDING) const incomingRequests = await Promise.all( incoming.map(async (account) => { - const userInfo = await serverProgram.getUser( - new PublicKey(account.from), - ) + const userInfo = await usersProgram.getUserInfo(account.from) return friendAccountToIncomingRequest(account, userInfo) }), ) const outgoingRequests = await Promise.all( outgoing.map(async (account) => { - const userInfo = await serverProgram.getUser(new PublicKey(account.to)) + const userInfo = await usersProgram.getUserInfo(account.to) return friendAccountToOutgoingRequest(account, userInfo) }), ) @@ -109,7 +108,7 @@ export default { friendAccount: FriendAccount, ) { const $SolanaManager: SolanaManager = Vue.prototype.$SolanaManager - const serverProgram: ServerProgram = new ServerProgram($SolanaManager) + const usersProgram: UsersProgram = new UsersProgram($SolanaManager) const $Crypto: Crypto = Vue.prototype.$Crypto // Check if the request was originally sent by the current user (outgoing) @@ -128,16 +127,18 @@ export default { friendKey, encryptedTextilePubkey, ) - const rawUser = await serverProgram.getUser(new PublicKey(friendKey)) - if (!rawUser) { + + const userInfo = await usersProgram.getUserInfo(friendKey) + + if (!userInfo) { throw new Error(FriendsError.FRIEND_INFO_NOT_FOUND) } const friend: Omit = { account: friendAccount, - name: rawUser.name, - profilePicture: rawUser.photoHash, - status: rawUser.status, + name: userInfo.name, + profilePicture: userInfo.photoHash, + status: userInfo.status, encryptedTextilePubkey, textilePubkey, item: {}, @@ -185,9 +186,7 @@ export default { const friendsProgram: FriendsProgram = new FriendsProgram($SolanaManager) - const serverProgram: ServerProgram = new ServerProgram($SolanaManager) - - const userAccount = $SolanaManager.getActiveAccount() + const usersProgram: UsersProgram = new UsersProgram($SolanaManager) friendsProgram.subscribeToFriendsEvents() @@ -195,9 +194,7 @@ export default { FriendsEvents.NEW_REQUEST, async (account) => { if (account) { - const userInfo = await serverProgram.getUser( - new PublicKey(account.from), - ) + const userInfo = await usersProgram.getUserInfo(account.from) commit( 'addIncomingRequest', friendAccountToIncomingRequest(account, userInfo), @@ -255,7 +252,7 @@ export default { const $SolanaManager: SolanaManager = Vue.prototype.$SolanaManager const $Crypto: Crypto = Vue.prototype.$Crypto const $TextileManager: TextileManager = Vue.prototype.$TextileManager - const serverProgram: ServerProgram = new ServerProgram($SolanaManager) + const usersProgram: UsersProgram = new UsersProgram($SolanaManager) const textilePublicKey = $TextileManager.getIdentityPublicKey() @@ -269,16 +266,10 @@ export default { throw new Error(AccountsError.PAYER_NOT_PRESENT) } - const userAccount = await $SolanaManager.getUserAccount() - - if (!userAccount) { - throw new Error(AccountsError.USER_DERIVATION_FAILED) - } - const friendsProgram: FriendsProgram = new FriendsProgram($SolanaManager) const friendAccountKey = await friendsProgram.computeFriendAccountKey( - userAccount.publicKey, + payerAccount.publicKey, friendToKey, ) @@ -287,7 +278,7 @@ export default { const friendAccountMirroredKey = await friendsProgram.computeFriendAccountKey( friendToKey, - userAccount.publicKey, + payerAccount.publicKey, ) let friendAccountMirroredInfo = await friendsProgram.getFriend( @@ -296,7 +287,7 @@ export default { if (!friendAccountInfo) { friendAccountInfo = await friendsProgram.createFriend( - userAccount.publicKey, + payerAccount.publicKey, friendToKey, ) } @@ -304,7 +295,7 @@ export default { if (!friendAccountMirroredInfo) { friendAccountMirroredInfo = await friendsProgram.createFriend( friendToKey, - userAccount.publicKey, + payerAccount.publicKey, ) } @@ -334,7 +325,7 @@ export default { const transactionHash = await friendsProgram.createFriendRequest( friendAccountKey, friendAccountMirroredKey, - userAccount, + payerAccount, friendToKey, Buffer.from(encryptedTextilePublicKey.padStart(128, '0')), ) @@ -345,9 +336,7 @@ export default { ) if (parsedFriendRequest) { - const userInfo = await serverProgram.getUser( - new PublicKey(parsedFriendRequest.to), - ) + const userInfo = await usersProgram.getUserInfo(parsedFriendRequest.to) commit( 'addOutgoingRequest', friendAccountToOutgoingRequest(parsedFriendRequest, userInfo), @@ -382,12 +371,6 @@ export default { throw new Error(AccountsError.PAYER_NOT_PRESENT) } - const userAccount = await $SolanaManager.getUserAccount() - - if (!userAccount) { - throw new Error(AccountsError.USER_DERIVATION_FAILED) - } - const friendsProgram: FriendsProgram = new FriendsProgram($SolanaManager) commit('updateIncomingRequest', { ...friendRequest, pending: true }) @@ -396,7 +379,7 @@ export default { const computedFriendAccountKey = await friendsProgram.computeFriendAccountKey( new PublicKey(account.from), - userAccount.publicKey, + payerAccount.publicKey, ) const friendFromKey = friendRequest.account.from @@ -413,7 +396,7 @@ export default { const transactionId = await friendsProgram.acceptFriendRequest( computedFriendAccountKey, new PublicKey(account.from), - userAccount, + payerAccount, Buffer.from(encryptedIdentityPublicKey.padStart(128, '0')), ) @@ -439,12 +422,6 @@ export default { throw new Error(AccountsError.PAYER_NOT_PRESENT) } - const userAccount = await $SolanaManager.getUserAccount() - - if (!userAccount) { - throw new Error(AccountsError.USER_DERIVATION_FAILED) - } - const friendsProgram: FriendsProgram = new FriendsProgram($SolanaManager) commit('updateIncomingRequest', { ...friendRequest, pending: true }) @@ -454,13 +431,13 @@ export default { const computedFriendAccountKey = await friendsProgram.computeFriendAccountKey( new PublicKey(account.from), - userAccount.publicKey, + payerAccount.publicKey, ) const transactionId = await friendsProgram.denyFriendRequest( computedFriendAccountKey, new PublicKey(account.from), - userAccount, + payerAccount, ) if (transactionId) { @@ -488,12 +465,6 @@ export default { throw new Error(AccountsError.PAYER_NOT_PRESENT) } - const userAccount = await $SolanaManager.getUserAccount() - - if (!userAccount) { - throw new Error(AccountsError.USER_DERIVATION_FAILED) - } - const friendsProgram: FriendsProgram = new FriendsProgram($SolanaManager) commit('updateOutgoingRequest', { ...friendRequest, pending: true }) @@ -502,13 +473,13 @@ export default { const computedFriendAccountMirroredKey = await friendsProgram.computeFriendAccountKey( - userAccount.publicKey, + payerAccount.publicKey, new PublicKey(account.to), ) const transactionId = await friendsProgram.removeFriendRequest( computedFriendAccountMirroredKey, - userAccount, + payerAccount, new PublicKey(account.to), ) @@ -537,19 +508,13 @@ export default { throw new Error(AccountsError.PAYER_NOT_PRESENT) } - const userAccount = await $SolanaManager.getUserAccount() - - if (!userAccount) { - throw new Error(AccountsError.USER_DERIVATION_FAILED) - } - const friendsProgram: FriendsProgram = new FriendsProgram($SolanaManager) const { account } = friend const transactionId = await friendsProgram.removeFriend( account, - userAccount, + payerAccount, ) if (transactionId) { @@ -566,7 +531,7 @@ export default { */ function friendAccountToIncomingRequest( friendAccount: FriendAccount, - userInfo: RawUser | null, + userInfo: UserInfo | null, ): IncomingRequest { return { requestId: friendAccount.accountId, @@ -585,7 +550,7 @@ function friendAccountToIncomingRequest( */ function friendAccountToOutgoingRequest( friendAccount: FriendAccount, - userInfo: RawUser | null, + userInfo: UserInfo | null, ): OutgoingRequest { return { requestId: friendAccount.accountId, diff --git a/store/getters.ts b/store/getters.ts new file mode 100644 index 0000000000..a858f0593e --- /dev/null +++ b/store/getters.ts @@ -0,0 +1,13 @@ +import { RootState } from '~/types/store/store' + +const getters = { + allPrerequisitesReady: (state: RootState): boolean => { + return ( + Boolean(state.accounts.active) && + state.textile.initialized && + state.webrtc.initialized + ) + }, +} + +export default getters diff --git a/store/prerequisites/Readme.md b/store/prerequisites/Readme.md deleted file mode 100644 index d8d416e61d..0000000000 --- a/store/prerequisites/Readme.md +++ /dev/null @@ -1,3 +0,0 @@ -# Prerequisites - -These are state values which are required to exist before the application can be considered "loaded". diff --git a/store/prerequisites/__snapshots__/getters.test.ts.snap b/store/prerequisites/__snapshots__/getters.test.ts.snap deleted file mode 100644 index 18d5eb449a..0000000000 --- a/store/prerequisites/__snapshots__/getters.test.ts.snap +++ /dev/null @@ -1,9 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`getters.default.allPrerequisitesReady 0 1`] = `false`; - -exports[`getters.default.allPrerequisitesReady 1 1`] = `false`; - -exports[`getters.default.allPrerequisitesReady 2 1`] = `true`; - -exports[`getters.default.allPrerequisitesReady 3 1`] = `false`; diff --git a/store/prerequisites/__snapshots__/mutations.test.ts.snap b/store/prerequisites/__snapshots__/mutations.test.ts.snap deleted file mode 100644 index 8d328ca82a..0000000000 --- a/store/prerequisites/__snapshots__/mutations.test.ts.snap +++ /dev/null @@ -1,41 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`mutations.default.resetState 0 1`] = `undefined`; - -exports[`mutations.default.resetState 1 1`] = `undefined`; - -exports[`mutations.default.resetState 2 1`] = `undefined`; - -exports[`mutations.default.resetState 3 1`] = `undefined`; - -exports[`mutations.default.resetState 4 1`] = `undefined`; - -exports[`mutations.default.setAccountsReady 0 1`] = `undefined`; - -exports[`mutations.default.setAccountsReady 1 1`] = `undefined`; - -exports[`mutations.default.setAccountsReady 2 1`] = `undefined`; - -exports[`mutations.default.setAccountsReady 3 1`] = `undefined`; - -exports[`mutations.default.setAccountsReady 4 1`] = `undefined`; - -exports[`mutations.default.setP2PReady 0 1`] = `undefined`; - -exports[`mutations.default.setP2PReady 1 1`] = `undefined`; - -exports[`mutations.default.setP2PReady 2 1`] = `undefined`; - -exports[`mutations.default.setP2PReady 3 1`] = `undefined`; - -exports[`mutations.default.setP2PReady 4 1`] = `undefined`; - -exports[`mutations.default.setTextileReady 0 1`] = `undefined`; - -exports[`mutations.default.setTextileReady 1 1`] = `undefined`; - -exports[`mutations.default.setTextileReady 2 1`] = `undefined`; - -exports[`mutations.default.setTextileReady 3 1`] = `undefined`; - -exports[`mutations.default.setTextileReady 4 1`] = `undefined`; diff --git a/store/prerequisites/__snapshots__/state.test.ts.snap b/store/prerequisites/__snapshots__/state.test.ts.snap deleted file mode 100644 index 6a8409e4b2..0000000000 --- a/store/prerequisites/__snapshots__/state.test.ts.snap +++ /dev/null @@ -1,9 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`state.default 0 1`] = ` -Object { - "accountsReady": false, - "p2pReady": true, - "textileReady": false, -} -`; diff --git a/store/prerequisites/actions.test.ts b/store/prerequisites/actions.test.ts deleted file mode 100644 index da7f4dd83e..0000000000 --- a/store/prerequisites/actions.test.ts +++ /dev/null @@ -1,23 +0,0 @@ -import Vue from 'vue' -import actions from '~/store/prerequisites/actions' -import * as Logger from '~/utilities/Logger' -import { Config } from '~/config' - -Vue.prototype.$Config = Config - -const DefaultLogger = Logger.default -Vue.prototype.$Logger = new DefaultLogger(Vue.prototype.$Config.debug) - -describe('actions.default.startup', () => { - test('0', async () => { - const LoggerPrototype = Vue.prototype.$Logger - - const dispatch = jest.fn() - LoggerPrototype.log = jest.fn() - - await actions.startup({ dispatch }) - - expect(LoggerPrototype.log).toHaveBeenCalled() - expect(LoggerPrototype.log).toHaveBeenCalledWith('WebRTC', 'Identified') - }) -}) diff --git a/store/prerequisites/actions.ts b/store/prerequisites/actions.ts deleted file mode 100644 index 6ec6b40f1c..0000000000 --- a/store/prerequisites/actions.ts +++ /dev/null @@ -1,15 +0,0 @@ -import Vue from 'vue' -import { PrerequisiteState } from './types' -import { ActionsArguments } from '~/types/store/store' - -export default { - /** - * @method startup DocsTODO - * @description - * @param - * @example - */ - async startup({ dispatch }: ActionsArguments) { - Vue.prototype.$Logger.log('WebRTC', 'Identified') - }, -} diff --git a/store/prerequisites/getters.test.ts b/store/prerequisites/getters.test.ts deleted file mode 100644 index 44f79b1a73..0000000000 --- a/store/prerequisites/getters.test.ts +++ /dev/null @@ -1,39 +0,0 @@ -import * as getters from '~/store/prerequisites/getters' - -describe('getters.default.allPrerequisitesReady', () => { - test('0', () => { - const result: any = getters.default.allPrerequisitesReady({ - accountsReady: true, - textileReady: false, - p2pReady: false, - }) - expect(result).toMatchSnapshot() - }) - - test('1', () => { - const result: any = getters.default.allPrerequisitesReady({ - accountsReady: true, - textileReady: true, - p2pReady: false, - }) - expect(result).toMatchSnapshot() - }) - - test('2', () => { - const result: any = getters.default.allPrerequisitesReady({ - accountsReady: true, - textileReady: true, - p2pReady: true, - }) - expect(result).toMatchSnapshot() - }) - - test('3', () => { - const result: any = getters.default.allPrerequisitesReady({ - accountsReady: true, - textileReady: false, - p2pReady: true, - }) - expect(result).toMatchSnapshot() - }) -}) diff --git a/store/prerequisites/getters.ts b/store/prerequisites/getters.ts deleted file mode 100644 index 93b973930e..0000000000 --- a/store/prerequisites/getters.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { PrerequisiteState } from './types' - -const getters = { - allPrerequisitesReady: (state: PrerequisiteState): boolean => { - return Object.values(state).reduce( - (stateLoaded, prerequisite) => stateLoaded && prerequisite, - true, - ) - }, -} - -export default getters diff --git a/store/prerequisites/mutations.test.ts b/store/prerequisites/mutations.test.ts deleted file mode 100644 index e0e7c40f49..0000000000 --- a/store/prerequisites/mutations.test.ts +++ /dev/null @@ -1,174 +0,0 @@ -import * as mutations from '~/store/prerequisites/mutations' - -describe('mutations.default.setTextileReady', () => { - test('0', () => { - const result: any = mutations.default.setTextileReady( - { accountsReady: true, textileReady: true, p2pReady: true }, - true, - ) - expect(result).toMatchSnapshot() - }) - - test('1', () => { - const result: any = mutations.default.setTextileReady( - { accountsReady: true, textileReady: false, p2pReady: false }, - false, - ) - expect(result).toMatchSnapshot() - }) - - test('2', () => { - const result: any = mutations.default.setTextileReady( - { accountsReady: true, textileReady: true, p2pReady: false }, - true, - ) - expect(result).toMatchSnapshot() - }) - - test('3', () => { - const result: any = mutations.default.setTextileReady( - { accountsReady: true, textileReady: false, p2pReady: false }, - true, - ) - expect(result).toMatchSnapshot() - }) - - test('4', () => { - const result: any = mutations.default.setTextileReady( - { accountsReady: true, textileReady: false, p2pReady: true }, - false, - ) - expect(result).toMatchSnapshot() - }) -}) - -describe('mutations.default.setP2PReady', () => { - test('0', () => { - const result: any = mutations.default.setP2PReady( - { accountsReady: true, textileReady: false, p2pReady: false }, - false, - ) - expect(result).toMatchSnapshot() - }) - - test('1', () => { - const result: any = mutations.default.setP2PReady( - { accountsReady: true, textileReady: true, p2pReady: false }, - true, - ) - expect(result).toMatchSnapshot() - }) - - test('2', () => { - const result: any = mutations.default.setP2PReady( - { accountsReady: true, textileReady: false, p2pReady: true }, - true, - ) - expect(result).toMatchSnapshot() - }) - - test('3', () => { - const result: any = mutations.default.setP2PReady( - { accountsReady: false, textileReady: false, p2pReady: true }, - true, - ) - expect(result).toMatchSnapshot() - }) - - test('4', () => { - const result: any = mutations.default.setP2PReady( - { accountsReady: false, textileReady: false, p2pReady: false }, - true, - ) - expect(result).toMatchSnapshot() - }) -}) - -describe('mutations.default.setAccountsReady', () => { - test('0', () => { - const result: any = mutations.default.setAccountsReady( - { accountsReady: true, textileReady: true, p2pReady: true }, - true, - ) - expect(result).toMatchSnapshot() - }) - - test('1', () => { - const result: any = mutations.default.setAccountsReady( - { accountsReady: false, textileReady: true, p2pReady: false }, - true, - ) - expect(result).toMatchSnapshot() - }) - - test('2', () => { - const result: any = mutations.default.setAccountsReady( - { accountsReady: false, textileReady: false, p2pReady: true }, - false, - ) - expect(result).toMatchSnapshot() - }) - - test('3', () => { - const result: any = mutations.default.setAccountsReady( - { accountsReady: false, textileReady: false, p2pReady: false }, - false, - ) - expect(result).toMatchSnapshot() - }) - - test('4', () => { - const result: any = mutations.default.setAccountsReady( - { accountsReady: true, textileReady: false, p2pReady: true }, - false, - ) - expect(result).toMatchSnapshot() - }) -}) - -describe('mutations.default.resetState', () => { - test('0', () => { - const result: any = mutations.default.resetState({ - accountsReady: false, - textileReady: true, - p2pReady: true, - }) - expect(result).toMatchSnapshot() - }) - - test('1', () => { - const result: any = mutations.default.resetState({ - accountsReady: true, - textileReady: false, - p2pReady: true, - }) - expect(result).toMatchSnapshot() - }) - - test('2', () => { - const result: any = mutations.default.resetState({ - accountsReady: false, - textileReady: true, - p2pReady: false, - }) - expect(result).toMatchSnapshot() - }) - - test('3', () => { - const result: any = mutations.default.resetState({ - accountsReady: true, - textileReady: true, - p2pReady: true, - }) - expect(result).toMatchSnapshot() - }) - - test('4', () => { - const result: any = mutations.default.resetState({ - accountsReady: true, - textileReady: false, - p2pReady: false, - }) - expect(result).toMatchSnapshot() - }) -}) diff --git a/store/prerequisites/mutations.ts b/store/prerequisites/mutations.ts deleted file mode 100644 index d32cdb97ff..0000000000 --- a/store/prerequisites/mutations.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { PrerequisiteState } from './types' -import getInitialState from './state' - -const mutations = { - setAccountsReady(state: PrerequisiteState, accountsReady: boolean) { - state.accountsReady = accountsReady - }, - setP2PReady(state: PrerequisiteState, p2pReady: boolean) { - state.p2pReady = p2pReady - }, - setTextileReady(state: PrerequisiteState, textileReady: boolean) { - state.textileReady = textileReady - }, - resetState(state: PrerequisiteState) { - const initialState = getInitialState() - state.accountsReady = initialState.accountsReady - state.p2pReady = initialState.p2pReady - state.textileReady = initialState.textileReady - }, -} - -export default mutations diff --git a/store/prerequisites/state.test.ts b/store/prerequisites/state.test.ts deleted file mode 100644 index f5a771b47d..0000000000 --- a/store/prerequisites/state.test.ts +++ /dev/null @@ -1,8 +0,0 @@ -import * as state from '~/store/prerequisites/state' - -describe('state.default', () => { - test('0', () => { - const result: any = state.default() - expect(result).toMatchSnapshot() - }) -}) diff --git a/store/prerequisites/state.ts b/store/prerequisites/state.ts deleted file mode 100644 index 9762beb185..0000000000 --- a/store/prerequisites/state.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { PrerequisiteState } from './types' - -const InitialPrerequisitesState = (): PrerequisiteState => ({ - accountsReady: false, - p2pReady: true, - textileReady: false, -}) - -export default InitialPrerequisitesState diff --git a/store/prerequisites/types.ts b/store/prerequisites/types.ts deleted file mode 100644 index e447dc4f2b..0000000000 --- a/store/prerequisites/types.ts +++ /dev/null @@ -1,5 +0,0 @@ -export interface PrerequisiteState { - accountsReady: boolean - textileReady: boolean - p2pReady: boolean -} diff --git a/store/textile/actions.ts b/store/textile/actions.ts index 88c7ab9298..6063f7cdda 100644 --- a/store/textile/actions.ts +++ b/store/textile/actions.ts @@ -10,6 +10,7 @@ import { MailboxSubscriptionType, Message } from '~/types/textile/mailbox' import { UploadDropItemType } from '~/types/files/file' import { db, DexieMessage } from '~/plugins/thirdparty/dexie' import { GroupChatManager } from '~/libraries/Textile/GroupChatManager' +import { FilSystem } from '~/libraries/Files/FilSystem' export default { /** @@ -32,8 +33,12 @@ export default { commit('textileInitialized', true) commit('accounts/updateTextilePubkey', textilePublicKey, { root: true }) - // Set Textile Ready prerequisite as true - commit('prerequisites/setTextileReady', true, { root: true }) + const fsExport = $TextileManager.bucket?.index + + if (fsExport) { + const $FileSystem: FilSystem = Vue.prototype.$FileSystem + $FileSystem.import(fsExport) + } }, /** * @description Fetches messages that comes from a specific user diff --git a/store/ui/actions.test.ts b/store/ui/actions.test.ts index 683452a31e..6ac3b54385 100644 --- a/store/ui/actions.test.ts +++ b/store/ui/actions.test.ts @@ -119,11 +119,6 @@ const initialRootState: any = { }, }, }, - prerequisites: { - accountsReady: true, - textileReady: true, - p2pReady: true, - }, settings: { audioInput: '', audioOutput: '', diff --git a/store/ui/mutations.test.ts b/store/ui/mutations.test.ts index 8111a1e9a9..df375abb4a 100644 --- a/store/ui/mutations.test.ts +++ b/store/ui/mutations.test.ts @@ -1,10 +1,10 @@ +import state from '../friends/state' import * as mutations from '~/store/ui/mutations' import { RegistrationStatus } from '~/store/accounts/types' import { DataStateType } from '~/store/dataState/types' import { CaptureMouseTypes } from '~/store/settings/types' import { FlairColors, ThemeNames } from '~/store/ui/types' -import state from '../friends/state' // So we don't have annoying snapshot fails. (https://stackoverflow.com/questions/42935903/jest-snapshot-testing-how-to-ignore-part-of-the-snapshot-file-in-jest-test-resu) Date.now = jest.fn(() => 1645617999076) @@ -2912,11 +2912,6 @@ describe('mutations', () => { }, }, }, - prerequisites: { - accountsReady: true, - textileReady: true, - p2pReady: true, - }, settings: { audioInput: '', audioOutput: '', diff --git a/store/webrtc/actions.ts b/store/webrtc/actions.ts index 753ebc718a..222c557e6c 100644 --- a/store/webrtc/actions.ts +++ b/store/webrtc/actions.ts @@ -6,7 +6,6 @@ import Crypto from '~/libraries/Crypto/Crypto' import { ActionsArguments } from '~/types/store/store' import WebRTC from '~/libraries/WebRTC/WebRTC' import Logger from '~/utilities/Logger' -import { TracksManager } from '~/libraries/WebRTC/TracksManager' export default { /** @@ -29,9 +28,11 @@ export default { $Logger.log('WebRTC', 'PEER_CONNECT', { peerId }) commit('setConnectedPeer', peerId) }) + $WebRTC.on('ERROR', () => { commit('setConnectedPeer', '') }) + commit('setInitialized', true) }, /** @@ -46,7 +47,6 @@ export default { identifier: string, ) { const $WebRTC: WebRTC = Vue.prototype.$WebRTC - const $TracksManager: TracksManager = Vue.prototype.$TracksManager const $Crypto: Crypto = Vue.prototype.$Crypto if (!$WebRTC.initialized) { @@ -73,13 +73,6 @@ export default { ) }) - // peer?.communicationBus.on('RAW_DATA', (message) => { - // if (message.data.type === 'CALL_DENIED') { - // peer?.call.hangUp() - // dispatch('hangUp') - // } - // }) - peer?.call.on('INCOMING_CALL', (data) => { // if incoming call is activer call return before toggling incoming call if (state.activeCall === data.peerId) { diff --git a/types/store/store.ts b/types/store/store.ts index 767829c6d6..74402b372d 100644 --- a/types/store/store.ts +++ b/types/store/store.ts @@ -3,15 +3,15 @@ import { Store, Commit, Dispatch } from 'vuex' import { AccountsState } from '~/store/accounts/types' import { DataState } from '~/store/dataState/types' import { FriendsState } from '~/store/friends/types' -import { PrerequisiteState } from '~/store/prerequisites/types' import { TextileState } from '~/store/textile/types' +import { WebRTCState } from '~/store/webrtc/types' export interface RootState { accounts: AccountsState dataState: DataState friends: FriendsState textile: TextileState - prerequisites: PrerequisiteState + webrtc: WebRTCState } export type RootStore = Store diff --git a/types/ui/friends.d.ts b/types/ui/friends.d.ts index b97dbe69f0..7173de9d74 100644 --- a/types/ui/friends.d.ts +++ b/types/ui/friends.d.ts @@ -1,5 +1,6 @@ import { FriendAccount } from '~/libraries/Solana/FriendsProgram/FriendsProgram.types' -import { RawUser, User } from '~/types/ui/user' +import { UserInfo } from '~/libraries/Solana/UsersProgram/UsersProgram' +import { User } from '~/types/ui/user' export interface EncryptedFriend extends User { encryptedTextilePubkey: string @@ -9,7 +10,7 @@ export interface FriendRequest { requestId: string account: FriendAccount pending: boolean - userInfo: RawUser + userInfo: UserInfo | null } export interface IncomingRequest extends FriendRequest { diff --git a/utilities/Hounddog.test.ts b/utilities/Hounddog.test.ts index f6a1309fa3..0f89e8977a 100644 --- a/utilities/Hounddog.test.ts +++ b/utilities/Hounddog.test.ts @@ -119,11 +119,6 @@ describe("Retrieving friend's profile", () => { }, }, }, - prerequisites: { - accountsReady: true, - textileReady: true, - p2pReady: true, - }, } beforeEach(() => {