Refactor shared metadata models, update browser extension to use vaultsync hook (#900)

This commit is contained in:
Leendert de Borst
2025-06-09 13:16:17 +02:00
committed by Leendert de Borst
parent ab6191ac62
commit 5ca29a33d0
26 changed files with 208 additions and 180 deletions

View File

@@ -1,19 +1,45 @@
import React from 'react';
import { EmailPreview } from '@/entrypoints/popup/components/EmailPreview';
import { useDb } from '@/entrypoints/popup/context/DbContext';
type EmailBlockProps = {
email: string;
isSupported: boolean;
}
/**
* Render the email block.
*/
const EmailBlock: React.FC<EmailBlockProps> = ({ email, isSupported }) => (
<>
{isSupported && <EmailPreview email={email} />}
</>
);
const EmailBlock: React.FC<EmailBlockProps> = ({ email }) => {
const dbContext = useDb();
/**
* Check if the email domain is supported.
*/
const isEmailDomainSupported = async (email: string): Promise<boolean> => {
const domain = email.split('@')[1]?.toLowerCase();
if (!domain) {
return false;
}
const vaultMetadata = await dbContext.getVaultMetadata();
const publicDomains = vaultMetadata?.publicEmailDomains ?? [];
const privateDomains = vaultMetadata?.privateEmailDomains ?? [];
return [...publicDomains, ...privateDomains].some(supportedDomain =>
domain === supportedDomain || domain.endsWith(`.${supportedDomain}`)
);
};
if (!isEmailDomainSupported(email)) {
return null;
}
return (
<>
{<EmailPreview email={email} />}
</>
);
};
export default EmailBlock;

View File

@@ -11,6 +11,7 @@ type AuthContextType = {
isLoggedIn: boolean;
isInitialized: boolean;
username: string | null;
initializeAuth: () => Promise<{ isLoggedIn: boolean }>;
setAuthTokens: (username: string, accessToken: string, refreshToken: string) => Promise<void>;
login: () => Promise<void>;
logout: (errorMessage?: string) => Promise<void>;
@@ -34,25 +35,29 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
const dbContext = useDb();
/**
* Check for tokens in browser local storage on initial load.
* Initialize the authentication state.
*
* @returns object containing whether the user is logged in.
*/
const initializeAuth = useCallback(async () : Promise<{ isLoggedIn: boolean }> => {
const accessToken = await storage.getItem('local:accessToken') as string;
const refreshToken = await storage.getItem('local:refreshToken') as string;
const username = await storage.getItem('local:username') as string;
if (accessToken && refreshToken && username) {
setUsername(username);
setIsLoggedIn(true);
}
setIsInitialized(true);
return { isLoggedIn };
}, [setUsername, setIsLoggedIn, isLoggedIn]);
/**
* Check for tokens in browser local storage on initial load when this context is mounted.
*/
useEffect(() => {
/**
* Initialize the authentication state.
*/
const initializeAuth = async () : Promise<void> => {
const accessToken = await storage.getItem('local:accessToken') as string;
const refreshToken = await storage.getItem('local:refreshToken') as string;
const username = await storage.getItem('local:username') as string;
if (accessToken && refreshToken && username) {
setUsername(username);
setIsLoggedIn(true);
}
setIsInitialized(true);
};
initializeAuth();
}, []);
}, [initializeAuth]);
/**
* Set auth tokens in browser local storage as part of the login process. After db is initialized, the login method should be called as well.
@@ -103,12 +108,13 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
isLoggedIn,
isInitialized,
username,
initializeAuth,
setAuthTokens,
login,
logout,
globalMessage,
clearGlobalMessage,
}), [isLoggedIn, isInitialized, username, globalMessage, setAuthTokens, login, logout, clearGlobalMessage]);
}), [isLoggedIn, isInitialized, username, initializeAuth, globalMessage, setAuthTokens, login, logout, clearGlobalMessage]);
return (
<AuthContext.Provider value={contextValue}>

View File

@@ -2,9 +2,10 @@ import React, { createContext, useContext, useState, useEffect, useCallback, use
import { sendMessage } from 'webext-bridge/popup';
import EncryptionUtility from '@/utils/EncryptionUtility';
import type { VaultMetadata } from '@/utils/shared/models/metadata';
import type { VaultResponse } from '@/utils/shared/models/webapi';
import SqliteClient from '@/utils/SqliteClient';
import { VaultResponse as messageVaultResponse } from '@/utils/types/messaging/VaultResponse';
import type { VaultResponse as messageVaultResponse } from '@/utils/types/messaging/VaultResponse';
type DbContextType = {
sqliteClient: SqliteClient | null;
@@ -12,9 +13,7 @@ type DbContextType = {
dbAvailable: boolean;
initializeDatabase: (vaultResponse: VaultResponse, derivedKey: string) => Promise<void>;
clearDatabase: () => void;
vaultRevision: number;
publicEmailDomains: string[];
privateEmailDomains: string[];
getVaultMetadata: () => Promise<VaultMetadata | null>;
}
const DbContext = createContext<DbContextType | undefined>(undefined);
@@ -38,20 +37,10 @@ export const DbProvider: React.FC<{ children: React.ReactNode }> = ({ children }
*/
const [dbAvailable, setDbAvailable] = useState(false);
/**
* Public email domains.
*/
const [publicEmailDomains, setPublicEmailDomains] = useState<string[]>([]);
/**
* Vault revision.
*/
const [vaultRevision, setVaultRevision] = useState(0);
/**
* Private email domains.
*/
const [privateEmailDomains, setPrivateEmailDomains] = useState<string[]>([]);
const [vaultMetadata, setVaultMetadata] = useState<VaultMetadata | null>(null);
const initializeDatabase = useCallback(async (vaultResponse: VaultResponse, derivedKey: string) => {
// Attempt to decrypt the blob.
@@ -67,9 +56,11 @@ export const DbProvider: React.FC<{ children: React.ReactNode }> = ({ children }
setSqliteClient(client);
setDbInitialized(true);
setDbAvailable(true);
setPublicEmailDomains(vaultResponse.vault.publicEmailDomainList);
setPrivateEmailDomains(vaultResponse.vault.privateEmailDomainList);
setVaultRevision(vaultResponse.vault.currentRevisionNumber);
setVaultMetadata({
publicEmailDomains: vaultResponse.vault.publicEmailDomainList,
privateEmailDomains: vaultResponse.vault.privateEmailDomainList,
vaultRevisionNumber: vaultResponse.vault.currentRevisionNumber,
});
/*
* Store encrypted vault in background worker.
@@ -90,9 +81,11 @@ export const DbProvider: React.FC<{ children: React.ReactNode }> = ({ children }
setSqliteClient(client);
setDbInitialized(true);
setDbAvailable(true);
setPublicEmailDomains(response.publicEmailDomains ?? []);
setPrivateEmailDomains(response.privateEmailDomains ?? []);
setVaultRevision(response.vaultRevisionNumber ?? 0);
setVaultMetadata({
publicEmailDomains: response.publicEmailDomains ?? [],
privateEmailDomains: response.privateEmailDomains ?? [],
vaultRevisionNumber: response.vaultRevisionNumber ?? 0,
});
} else {
setDbInitialized(true);
setDbAvailable(false);
@@ -104,6 +97,13 @@ export const DbProvider: React.FC<{ children: React.ReactNode }> = ({ children }
}
}, []);
/**
* Get the vault metadata.
*/
const getVaultMetadata = useCallback(async () : Promise<VaultMetadata | null> => {
return vaultMetadata;
}, [vaultMetadata]);
/**
* Check if database is initialized and try to retrieve vault from background
*/
@@ -128,10 +128,8 @@ export const DbProvider: React.FC<{ children: React.ReactNode }> = ({ children }
dbAvailable,
initializeDatabase,
clearDatabase,
vaultRevision,
publicEmailDomains,
privateEmailDomains
}), [sqliteClient, dbInitialized, dbAvailable, initializeDatabase, clearDatabase, vaultRevision, publicEmailDomains, privateEmailDomains]);
getVaultMetadata,
}), [sqliteClient, dbInitialized, dbAvailable, initializeDatabase, clearDatabase, getVaultMetadata]);
return (
<DbContext.Provider value={contextValue}>

View File

@@ -1,10 +1,10 @@
import { useCallback } from 'react';
import { sendMessage } from 'webext-bridge/popup';
import { useAuth } from '@/entrypoints/popup/context/AuthContext';
import { useDb } from '@/entrypoints/popup/context/DbContext';
import { useWebApi } from '@/entrypoints/popup/context/WebApiContext';
import { AppInfo } from '@/utils/AppInfo';
import type { VaultResponse } from '@/utils/shared/models/webapi';
/**
@@ -13,9 +13,10 @@ import type { VaultResponse } from '@/utils/shared/models/webapi';
const withMinimumDelay = async <T>(
operation: () => Promise<T>,
minDelayMs: number,
initialSync: boolean
enableDelay: boolean = true
): Promise<T> => {
if (!initialSync) {
if (!enableDelay) {
// If delay is disabled, return the result immediately.
return operation();
}
@@ -35,7 +36,7 @@ type VaultSyncOptions = {
onSuccess?: (hasNewVault: boolean) => void;
onError?: (error: string) => void;
onStatus?: (message: string) => void;
onOffline?: () => void;
_onOffline?: () => void;
}
/**
@@ -49,7 +50,10 @@ export const useVaultSync = () : {
const webApi = useWebApi();
const syncVault = useCallback(async (options: VaultSyncOptions = {}) => {
const { initialSync = false, onSuccess, onError, onStatus, onOffline } = options;
const { initialSync = false, onSuccess, onError, onStatus, _onOffline } = options;
// For the initial sync, we add an artifical delay to various steps which makes it feel more fluid.
const enableDelay = initialSync;
try {
const { isLoggedIn } = await authContext.initializeAuth();
@@ -61,26 +65,15 @@ export const useVaultSync = () : {
// Check app status and vault revision
onStatus?.('Checking vault updates');
const statusResponse = await withMinimumDelay(
() => webApi.getStatus(),
300,
initialSync
);
const statusResponse = await withMinimumDelay(() => webApi.getStatus(), 300, enableDelay);
// Check if server is actually available, 0.0.0 indicates connection error which triggers offline mode.
if (statusResponse.serverVersion === '0.0.0') {
// Server is not available, go into offline mode
onOffline?.();
return false;
// Offline mode is not implemented for browser extension yet, let it fail below due to the validateStatusResponse check.
}
if (!statusResponse.clientVersionSupported) {
const statusError = 'This version of the AliasVault mobile app is not supported by the server anymore. Please update your app to the latest version.';
onError?.(statusError);
return false;
}
if (!AppInfo.isServerVersionSupported(statusResponse.serverVersion)) {
const statusError = 'The AliasVault server needs to be updated to a newer version in order to use this mobile app. Please contact support if you need help.';
const statusError = webApi.validateStatusResponse(statusResponse);
if (statusError) {
onError?.(statusError);
return false;
}
@@ -97,11 +90,7 @@ export const useVaultSync = () : {
if (statusResponse.vaultRevision > vaultRevisionNumber) {
onStatus?.('Syncing updated vault');
const vaultResponseJson = await withMinimumDelay(
() => webApi.get<VaultResponse>('Vault'),
1000,
initialSync
);
const vaultResponseJson = await withMinimumDelay(() => webApi.get<VaultResponse>('Vault'), 1000, enableDelay);
const vaultError = webApi.validateVaultResponse(vaultResponseJson as VaultResponse);
if (vaultError) {
@@ -122,7 +111,9 @@ export const useVaultSync = () : {
}
try {
await dbContext.initializeDatabase(vaultResponseJson as VaultResponse);
// Get derived key from background worker
const passwordHashBase64 = await sendMessage('GET_DERIVED_KEY', {}, 'background') as string;
await dbContext.initializeDatabase(vaultResponseJson as VaultResponse, passwordHashBase64);
onSuccess?.(true);
return true;
} catch {
@@ -131,11 +122,7 @@ export const useVaultSync = () : {
}
}
await withMinimumDelay(
() => Promise.resolve(onSuccess?.(false)),
300,
initialSync
);
await withMinimumDelay(() => Promise.resolve(onSuccess?.(false)), 300, enableDelay);
return false;
} catch (err) {
const errorMessage = err instanceof Error ? err.message : 'Unknown error during vault sync';

View File

@@ -50,23 +50,6 @@ const CredentialDetails: React.FC = () => {
window.close();
};
/**
* Check if the email domain is supported.
*/
const isEmailDomainSupported = (email: string): boolean => {
const domain = email.split('@')[1]?.toLowerCase();
if (!domain) {
return false;
}
const publicDomains = dbContext.publicEmailDomains ?? [];
const privateDomains = dbContext.privateEmailDomains ?? [];
return [...publicDomains, ...privateDomains].some(supportedDomain =>
domain === supportedDomain || domain.endsWith(`.${supportedDomain}`)
);
};
useEffect(() => {
if (isPopup()) {
window.history.replaceState({}, '', `popup.html#/credentials`);
@@ -101,7 +84,6 @@ const CredentialDetails: React.FC = () => {
{credential.Alias?.Email && (
<EmailBlock
email={credential.Alias.Email}
isSupported={isEmailDomainSupported(credential.Alias.Email)}
/>
)}
<NotesBlock notes={credential.Notes} />

View File

@@ -1,5 +1,4 @@
import React, { useState, useEffect, useCallback } from 'react';
import { sendMessage } from 'webext-bridge/popup';
import CredentialCard from '@/entrypoints/popup/components/CredentialCard';
import LoadingSpinner from '@/entrypoints/popup/components/LoadingSpinner';
@@ -7,9 +6,9 @@ import ReloadButton from '@/entrypoints/popup/components/ReloadButton';
import { useDb } from '@/entrypoints/popup/context/DbContext';
import { useLoading } from '@/entrypoints/popup/context/LoadingContext';
import { useWebApi } from '@/entrypoints/popup/context/WebApiContext';
import { useVaultSync } from '@/entrypoints/popup/hooks/useVaultSync';
import type { Credential } from '@/utils/shared/models/vault';
import type { VaultResponse } from '@/utils/shared/models/webapi';
import { useMinDurationLoading } from '@/hooks/useMinDurationLoading';
@@ -19,6 +18,7 @@ import { useMinDurationLoading } from '@/hooks/useMinDurationLoading';
const CredentialsList: React.FC = () => {
const dbContext = useDb();
const webApi = useWebApi();
const { syncVault } = useVaultSync();
const [credentials, setCredentials] = useState<Credential[]>([]);
const [searchTerm, setSearchTerm] = useState('');
const { showLoading, hideLoading, setIsInitialLoading } = useLoading();
@@ -36,53 +36,36 @@ const CredentialsList: React.FC = () => {
return;
}
// Do status check first to ensure the extension is (still) supported.
const statusResponse = await webApi.getStatus();
const statusError = webApi.validateStatusResponse(statusResponse);
if (statusError !== null) {
await webApi.logout(statusError);
return;
}
try {
// If the vault revision is the same or lower, (re)load existing credentials.
if (statusResponse.vaultRevision <= dbContext.vaultRevision) {
const results = dbContext.sqliteClient.getAllCredentials();
setCredentials(results);
return;
}
/**
* If the vault revision is higher, fetch the latest vault and initialize the SQLite context again.
* This will trigger a new credentials list refresh.
*/
const vaultResponseJson = await webApi.get<VaultResponse>('Vault');
const vaultError = webApi.validateVaultResponse(vaultResponseJson);
if (vaultError) {
await webApi.logout(vaultError);
hideLoading();
return;
}
// Get derived key from background worker
const passwordHashBase64 = await sendMessage('GET_DERIVED_KEY', {}, 'background') as string;
// Initialize the SQLite context again with the newly retrieved decrypted blob)
try {
await dbContext.initializeDatabase(vaultResponseJson, passwordHashBase64);
} catch {
// Sync vault and load credentials
await syncVault({
/**
* If error occurs during database initialization, it most likely has to do with decryption that
* failed. This is most likely due to the user changing their password.
* So we logout the user here to force them to re-authenticate.
* On success.
*/
await webApi.logout('Vault could not be decrypted, please re-authenticate.');
}
onSuccess: async (_hasNewVault) => {
// Refresh credentials list, whether there is a new vault or not.
const results = dbContext.sqliteClient?.getAllCredentials() ?? [];
setCredentials(results);
},
/**
* On offline.
*/
onOffline: () => {
// Not implemented for browser extension yet.
},
/**
* On error.
*/
onError: async (error) => {
console.error('Error syncing vault:', error);
await webApi.logout('Error while syncing vault, please re-authenticate.');
},
});
} catch (err) {
console.error('Refresh error:', err);
console.error('Error refreshing credentials:', err);
await webApi.logout('Error while syncing vault, please re-authenticate.');
}
}, [dbContext, webApi, hideLoading]);
}, [dbContext, webApi, syncVault]);
/**
* Manually refresh the credentials list.
@@ -103,7 +86,8 @@ const CredentialsList: React.FC = () => {
const refreshCredentials = async () : Promise<void> => {
if (dbContext?.sqliteClient) {
setIsLoading(true);
await onRefresh();
const results = dbContext.sqliteClient?.getAllCredentials() ?? [];
setCredentials(results);
setIsLoading(false);
// Hide the global app initial loading state after the credentials list is loaded.
@@ -112,7 +96,7 @@ const CredentialsList: React.FC = () => {
};
refreshCredentials();
}, [dbContext?.sqliteClient, onRefresh, setIsLoading, setIsInitialLoading]);
}, [dbContext?.sqliteClient, setIsLoading, setIsInitialLoading]);
// Add this function to filter credentials
const filteredCredentials = credentials.filter(cred => {

View File

@@ -8,7 +8,7 @@ import { useWebApi } from '@/entrypoints/popup/context/WebApiContext';
import ConversionUtility from '@/entrypoints/popup/utils/ConversionUtility';
import EncryptionUtility from '@/utils/EncryptionUtility';
import type { Attachment, Email } from '@/utils/shared/models/webapi';
import type { EmailAttachment, Email } from '@/utils/shared/models/webapi';
import { useMinDurationLoading } from '@/hooks/useMinDurationLoading';
@@ -112,7 +112,7 @@ const EmailDetails: React.FC = () => {
/**
* Handle downloading an attachment.
*/
const handleDownloadAttachment = async (attachment: Attachment): Promise<void> => {
const handleDownloadAttachment = async (attachment: EmailAttachment): Promise<void> => {
try {
// Get the encrypted attachment bytes from the API
const base64EncryptedAttachment = await webApi.downloadBlobAndConvertToBase64(`Email/${id}/attachments/${attachment.id}`);

View File

@@ -0,0 +1,18 @@
type VaultMetadata = {
publicEmailDomains: string[];
privateEmailDomains: string[];
vaultRevisionNumber: number;
};
/**
* These parameters for deriving encryption key from plain text password. These are stored
* as metadata in the vault upon initial login, and are used to derive the encryption key
* from the plain text password in the unlock screen.
*/
type EncryptionKeyDerivationParams = {
encryptionType: string;
encryptionSettings: string;
salt: string;
};
export type { EncryptionKeyDerivationParams, VaultMetadata };

View File

@@ -0,0 +1,3 @@
//# sourceMappingURL=index.js.map
//# sourceMappingURL=index.js.map

View File

@@ -144,7 +144,7 @@ type MailboxBulkResponse = {
/**
* Email attachment type.
*/
type Attachment = {
type EmailAttachment = {
/** The ID of the attachment */
id: number;
/** The ID of the email the attachment belongs to */
@@ -190,7 +190,7 @@ type Email = {
/** The public key of the user used to encrypt the symmetric key */
encryptionKey: string;
/** The attachments of the email */
attachments: Attachment[];
attachments: EmailAttachment[];
};
/**
@@ -353,4 +353,4 @@ declare enum AuthEventType {
AccountDeletion = 99
}
export { type Attachment, AuthEventType, type AuthLogModel, type BadRequestResponse, type DeleteAccountInitiateRequest, type DeleteAccountInitiateResponse, type DeleteAccountRequest, type Email, type FaviconExtractModel, type LoginRequest, type LoginResponse, type MailboxBulkRequest, type MailboxBulkResponse, type MailboxEmail, type PasswordChangeInitiateResponse, type RefreshToken, type StatusResponse, type ValidateLoginRequest, type ValidateLoginRequest2Fa, type ValidateLoginResponse, type Vault, type VaultPasswordChangeRequest, type VaultPostResponse, type VaultResponse };
export { AuthEventType, type AuthLogModel, type BadRequestResponse, type DeleteAccountInitiateRequest, type DeleteAccountInitiateResponse, type DeleteAccountRequest, type Email, type EmailAttachment, type FaviconExtractModel, type LoginRequest, type LoginResponse, type MailboxBulkRequest, type MailboxBulkResponse, type MailboxEmail, type PasswordChangeInitiateResponse, type RefreshToken, type StatusResponse, type ValidateLoginRequest, type ValidateLoginRequest2Fa, type ValidateLoginResponse, type Vault, type VaultPasswordChangeRequest, type VaultPostResponse, type VaultResponse };

View File

@@ -11,10 +11,10 @@ import { StyleSheet, View, Text, SafeAreaView, TextInput, TouchableOpacity, Acti
import { AppInfo } from '@/utils/AppInfo';
import ConversionUtility from '@/utils/ConversionUtility';
import EncryptionUtility from '@/utils/EncryptionUtility';
import type { EncryptionKeyDerivationParams } from '@/utils/shared/models/metadata';
import type { LoginResponse, VaultResponse } from '@/utils/shared/models/webapi';
import { SrpUtility } from '@/utils/SrpUtility';
import { ApiAuthError } from '@/utils/types/errors/ApiAuthError';
import { EncryptionKeyDerivationParams } from '@/utils/types/messaging/EncryptionKeyDerivationParams';
import { useColors } from '@/hooks/useColorScheme';
import { useVaultSync } from '@/hooks/useVaultSync';

View File

@@ -1,9 +1,8 @@
import React, { createContext, useContext, useState, useEffect, useCallback, useMemo } from 'react';
import type { EncryptionKeyDerivationParams, VaultMetadata } from '@/utils/shared/models/metadata';
import type { VaultResponse } from '@/utils/shared/models/webapi';
import SqliteClient from '@/utils/SqliteClient';
import { EncryptionKeyDerivationParams } from '@/utils/types/messaging/EncryptionKeyDerivationParams';
import { VaultMetadata } from '@/utils/types/messaging/VaultMetadata';
import NativeVaultManager from '@/specs/NativeVaultManager';

View File

@@ -3,8 +3,8 @@ import Toast from 'react-native-toast-message';
import srp from 'secure-remote-password/client';
import EncryptionUtility from '@/utils/EncryptionUtility';
import type { EncryptionKeyDerivationParams } from '@/utils/shared/models/metadata';
import type { PasswordChangeInitiateResponse, Vault, VaultPasswordChangeRequest } from '@/utils/shared/models/webapi';
import { EncryptionKeyDerivationParams } from '@/utils/types/messaging/EncryptionKeyDerivationParams';
import { useVaultSync } from '@/hooks/useVaultSync';

View File

@@ -13,9 +13,10 @@ import { useWebApi } from '@/context/WebApiContext';
const withMinimumDelay = async <T>(
operation: () => Promise<T>,
minDelayMs: number,
initialSync: boolean
enableDelay: boolean = true
): Promise<T> => {
if (!initialSync) {
if (!enableDelay) {
// If delay is disabled, return the result immediately.
return operation();
}
@@ -51,6 +52,9 @@ export const useVaultSync = () : {
const syncVault = useCallback(async (options: VaultSyncOptions = {}) => {
const { initialSync = false, onSuccess, onError, onStatus, onOffline } = options;
// For the initial sync, we add an artifical delay to various steps which makes it feel more fluid.
const enableDelay = initialSync;
try {
const { isLoggedIn } = await authContext.initializeAuth();
@@ -61,11 +65,7 @@ export const useVaultSync = () : {
// Check app status and vault revision
onStatus?.('Checking vault updates');
const statusResponse = await withMinimumDelay(
() => webApi.getStatus(),
300,
initialSync
);
const statusResponse = await withMinimumDelay(() => webApi.getStatus(), 300, enableDelay);
if (statusResponse.serverVersion === '0.0.0') {
// Server is not available, go into offline mode
@@ -94,11 +94,7 @@ export const useVaultSync = () : {
if (statusResponse.vaultRevision > vaultRevisionNumber) {
onStatus?.('Syncing updated vault');
const vaultResponseJson = await withMinimumDelay(
() => webApi.get<VaultResponse>('Vault'),
1000,
initialSync
);
const vaultResponseJson = await withMinimumDelay(() => webApi.get<VaultResponse>('Vault'), 1000, enableDelay);
const vaultError = webApi.validateVaultResponse(vaultResponseJson as VaultResponse);
if (vaultError) {
@@ -124,11 +120,7 @@ export const useVaultSync = () : {
}
}
await withMinimumDelay(
() => Promise.resolve(onSuccess?.(false)),
300,
initialSync
);
await withMinimumDelay(() => Promise.resolve(onSuccess?.(false)), 300, enableDelay);
return false;
} catch (err) {
const errorMessage = err instanceof Error ? err.message : 'Unknown error during vault sync';

View File

@@ -1,6 +1,5 @@
import type { EncryptionKeyDerivationParams, VaultMetadata } from '@/utils/shared/models/metadata';
import type { Credential, EncryptionKey, PasswordSettings, TotpCode } from '@/utils/shared/models/vault';
import { EncryptionKeyDerivationParams } from '@/utils/types/messaging/EncryptionKeyDerivationParams';
import { VaultMetadata } from '@/utils/types/messaging/VaultMetadata';
import NativeVaultManager from '@/specs/NativeVaultManager';

View File

@@ -0,0 +1,18 @@
type VaultMetadata = {
publicEmailDomains: string[];
privateEmailDomains: string[];
vaultRevisionNumber: number;
};
/**
* These parameters for deriving encryption key from plain text password. These are stored
* as metadata in the vault upon initial login, and are used to derive the encryption key
* from the plain text password in the unlock screen.
*/
type EncryptionKeyDerivationParams = {
encryptionType: string;
encryptionSettings: string;
salt: string;
};
export type { EncryptionKeyDerivationParams, VaultMetadata };

View File

@@ -0,0 +1,3 @@
//# sourceMappingURL=index.js.map
//# sourceMappingURL=index.js.map

View File

@@ -144,7 +144,7 @@ type MailboxBulkResponse = {
/**
* Email attachment type.
*/
type Attachment = {
type EmailAttachment = {
/** The ID of the attachment */
id: number;
/** The ID of the email the attachment belongs to */
@@ -190,7 +190,7 @@ type Email = {
/** The public key of the user used to encrypt the symmetric key */
encryptionKey: string;
/** The attachments of the email */
attachments: Attachment[];
attachments: EmailAttachment[];
};
/**
@@ -353,4 +353,4 @@ declare enum AuthEventType {
AccountDeletion = 99
}
export { type Attachment, AuthEventType, type AuthLogModel, type BadRequestResponse, type DeleteAccountInitiateRequest, type DeleteAccountInitiateResponse, type DeleteAccountRequest, type Email, type FaviconExtractModel, type LoginRequest, type LoginResponse, type MailboxBulkRequest, type MailboxBulkResponse, type MailboxEmail, type PasswordChangeInitiateResponse, type RefreshToken, type StatusResponse, type ValidateLoginRequest, type ValidateLoginRequest2Fa, type ValidateLoginResponse, type Vault, type VaultPasswordChangeRequest, type VaultPostResponse, type VaultResponse };
export { AuthEventType, type AuthLogModel, type BadRequestResponse, type DeleteAccountInitiateRequest, type DeleteAccountInitiateResponse, type DeleteAccountRequest, type Email, type EmailAttachment, type FaviconExtractModel, type LoginRequest, type LoginResponse, type MailboxBulkRequest, type MailboxBulkResponse, type MailboxEmail, type PasswordChangeInitiateResponse, type RefreshToken, type StatusResponse, type ValidateLoginRequest, type ValidateLoginRequest2Fa, type ValidateLoginResponse, type Vault, type VaultPasswordChangeRequest, type VaultPostResponse, type VaultResponse };

View File

@@ -17,7 +17,7 @@ echo "📦 Building $package_name..."
npm install && npm run lint && npm run build
dist_path="dist"
files_to_copy=("webapi" "vault")
files_to_copy=("webapi" "vault" "metadata")
for target in "${TARGETS[@]}"; do
echo "📂 Copying $package_name$target"

View File

@@ -0,0 +1,7 @@
/**
* Export all metadata models that are associated with a vault and returned by the web API.
* These models are stored locally in the client to make local (offline) key derivation and
* optimal vault sync possible.
*/
export * from './VaultMetadata';
export * from './EncryptionKeyDerivationParams';

View File

@@ -1,3 +1,6 @@
/**
* Export all vault entity models that match the vault SQLite datamodel.
*/
export * from './EncryptionKey';
export * from './PasswordSettings';
export * from './TotpCode';

View File

@@ -1,4 +1,4 @@
import { Attachment } from "./Attachment";
import { EmailAttachment } from "./EmailAttachment";
export type Email = {
/** The body of the email message */
@@ -47,5 +47,5 @@ export type Email = {
encryptionKey: string;
/** The attachments of the email */
attachments: Attachment[];
attachments: EmailAttachment[];
}

View File

@@ -1,7 +1,7 @@
/**
* Email attachment type.
*/
export type Attachment = {
export type EmailAttachment = {
/** The ID of the attachment */
id: number;

View File

@@ -1,3 +1,6 @@
/**
* Export all web API models that are returned by the web API.
*/
export * from './VaultResponse';
export * from './Vault';
export * from './VaultPostResponse';
@@ -7,7 +10,7 @@ export * from './ValidateLogin';
export * from './MailboxBulk';
export * from './MailboxEmail';
export * from './Email';
export * from './Attachment';
export * from './EmailAttachment';
export * from './AuthLog';
export * from './RefreshToken';
export * from './FaviconExtractModel';