Refactor browser extension and mobile app to use const for trash retention duration (#1967)

This commit is contained in:
Leendert de Borst
2026-04-26 12:38:30 +02:00
committed by Leendert de Borst
parent 4c15c53060
commit 8537fa8a2a
8 changed files with 38 additions and 26 deletions

View File

@@ -2,6 +2,7 @@
import * as OTPAuth from 'otpauth';
import { storage } from 'wxt/utils/storage';
import { TRASH_RETENTION_DAYS } from '@/utils/constants/vault';
import type { EncryptionKeyDerivationParams } from '@/utils/dist/core/models/metadata';
import { FieldKey, ItemTypes, createSystemField, type Item } from '@/utils/dist/core/models/vault';
import type { Vault, VaultResponse, VaultPostResponse } from '@/utils/dist/core/models/webapi';
@@ -762,7 +763,7 @@ export async function handleClearPersistedFormValues(): Promise<void> {
/**
* Upload a new version of the vault to the server using the provided sqlite client.
* Prunes expired trash items (older than 30 days) before uploading.
* Prunes expired trash items before uploading.
*/
async function uploadNewVaultToServer(sqliteClient: SqliteClient) : Promise<VaultPostResponse> {
let updatedVaultData = sqliteClient.exportToBase64();
@@ -775,11 +776,11 @@ async function uploadNewVaultToServer(sqliteClient: SqliteClient) : Promise<Vaul
/**
* Prune expired items from trash before uploading.
* Items that have been in trash (DeletedAt set) for more than 30 days
* Items that have been in trash (DeletedAt set) longer than TRASH_RETENTION_DAYS
* are permanently deleted (IsDeleted = true) as part of the sync process.
*/
try {
const pruneResult = await vaultMergeService.prune(updatedVaultData, 30);
const pruneResult = await vaultMergeService.prune(updatedVaultData, TRASH_RETENTION_DAYS);
if (pruneResult.success && pruneResult.statementCount > 0) {
console.info(`[VaultSync] Pruned expired items from trash (${pruneResult.statementCount} statements)`);
updatedVaultData = pruneResult.prunedVaultBase64;

View File

@@ -2,12 +2,14 @@ import React, { useState, useEffect, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import ConfirmDeleteModal from '@/entrypoints/popup/components/Dialogs/ConfirmDeleteModal';
import ItemIcon from '@/entrypoints/popup/components/Items/ItemIcon';
import LoadingSpinner from '@/entrypoints/popup/components/LoadingSpinner';
import PageTitle from '@/entrypoints/popup/components/PageTitle';
import { useDb } from '@/entrypoints/popup/context/DbContext';
import { useHeaderButtons } from '@/entrypoints/popup/context/HeaderButtonsContext';
import { useVaultMutate } from '@/entrypoints/popup/hooks/useVaultMutate';
import { TRASH_RETENTION_DAYS } from '@/utils/constants/vault';
import type { Item } from '@/utils/dist/core/models/vault';
import { useMinDurationLoading } from '@/hooks/useMinDurationLoading';
@@ -15,10 +17,10 @@ import { useMinDurationLoading } from '@/hooks/useMinDurationLoading';
/**
* Calculate days remaining until permanent deletion.
* @param deletedAt - ISO timestamp when item was deleted
* @param retentionDays - Number of days to retain (default 30)
* @param retentionDays - Number of days to retain (defaults to TRASH_RETENTION_DAYS)
* @returns Number of days remaining, or 0 if already expired
*/
const getDaysRemaining = (deletedAt: string, retentionDays: number = 30): number => {
const getDaysRemaining = (deletedAt: string, retentionDays: number = TRASH_RETENTION_DAYS): number => {
const deletedDate = new Date(deletedAt);
const expiryDate = new Date(deletedDate.getTime() + retentionDays * 24 * 60 * 60 * 1000);
const now = new Date();
@@ -162,19 +164,19 @@ const RecentlyDeleted: React.FC = () => {
{items.length === 0 ? (
<div className="text-gray-500 dark:text-gray-400 space-y-2 mb-10">
<p>{t('recentlyDeleted.noItems')}</p>
<p className="text-sm">{t('recentlyDeleted.noItemsDescription')}</p>
<p className="text-sm">{t('recentlyDeleted.noItemsDescription', { days: TRASH_RETENTION_DAYS })}</p>
</div>
) : (
<>
<p className="text-sm text-gray-500 dark:text-gray-400 mb-4">
{t('recentlyDeleted.description')}
{t('recentlyDeleted.description', { days: TRASH_RETENTION_DAYS })}
</p>
<ul className="space-y-2">
{items.map(item => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const deletedAt = (item as any).DeletedAt;
const daysRemaining = deletedAt ? getDaysRemaining(deletedAt) : 30;
const daysRemaining = deletedAt ? getDaysRemaining(deletedAt) : TRASH_RETENTION_DAYS;
return (
<li key={item.Id} className="relative">
@@ -183,13 +185,7 @@ const RecentlyDeleted: React.FC = () => {
{/* Item card content (simplified) */}
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2">
{item.Logo && (
<img
src={`data:image/png;base64,${btoa(String.fromCharCode(...item.Logo))}`}
alt=""
className="w-6 h-6 rounded"
/>
)}
<ItemIcon item={item} className="w-6 h-6 rounded" />
<span className="font-medium text-gray-900 dark:text-white truncate">
{item.Name || t('items.untitled')}
</span>

View File

@@ -507,8 +507,8 @@
"recentlyDeleted": {
"title": "Recently Deleted",
"noItems": "No deleted items",
"noItemsDescription": "Items you delete will appear here for 30 days before being permanently removed.",
"description": "These items will be permanently deleted after 30 days. You can restore them or delete them immediately.",
"noItemsDescription": "Items you delete will appear here for {{days}} days before being permanently removed.",
"description": "These items will be permanently deleted after {{days}} days. You can restore them or delete them immediately.",
"restore": "Restore",
"deletePermanently": "Delete Permanently",
"emptyAll": "Empty All",

View File

@@ -1,6 +1,8 @@
import initSqlJs, { Database, SqlJsStatic, SqlValue } from 'sql.js';
import { browser } from 'wxt/browser';
import { TRASH_RETENTION_DAYS } from '@/utils/constants/vault';
import init, { getSyncableTableNames, mergeVaults, pruneVault } from './dist/core/rust/aliasvault_core.js';
/**
@@ -244,10 +246,10 @@ export class VaultMergeService {
* 5. Export pruned database
*
* @param vaultBase64 - The vault as base64 SQLite
* @param retentionDays - Number of days to keep items in trash (default: 30)
* @param retentionDays - Number of days to keep items in trash (defaults to TRASH_RETENTION_DAYS)
* @returns PruneResult with the pruned vault as base64
*/
public async prune(vaultBase64: string, retentionDays: number = 30): Promise<PruneResult> {
public async prune(vaultBase64: string, retentionDays: number = TRASH_RETENTION_DAYS): Promise<PruneResult> {
try {
// Initialize Rust WASM
await this.initRust();

View File

@@ -0,0 +1,6 @@
/**
* Number of days that soft-deleted items stay in the "Recently Deleted" trash
* before being permanently pruned during vault sync. Mirrors the server-side
* default exposed via Config.TrashRetentionDays in the Blazor client.
*/
export const TRASH_RETENTION_DAYS = 30;

View File

@@ -21,15 +21,16 @@ import { ItemIcon } from '@/components/items/ItemIcon';
import { ThemedContainer } from '@/components/themed/ThemedContainer';
import { ThemedScrollView } from '@/components/themed/ThemedScrollView';
import { ThemedText } from '@/components/themed/ThemedText';
import { TRASH_RETENTION_DAYS } from '@/constants/vault';
import { useDb } from '@/context/DbContext';
/**
* Calculate days remaining until permanent deletion.
* @param deletedAt - ISO timestamp when item was deleted
* @param retentionDays - Number of days to retain (default 30)
* @param retentionDays - Number of days to retain (defaults to TRASH_RETENTION_DAYS)
* @returns Number of days remaining, or 0 if already expired
*/
const getDaysRemaining = (deletedAt: string, retentionDays: number = 30): number => {
const getDaysRemaining = (deletedAt: string, retentionDays: number = TRASH_RETENTION_DAYS): number => {
const deletedDate = new Date(deletedAt);
const expiryDate = new Date(deletedDate.getTime() + retentionDays * 24 * 60 * 60 * 1000);
const now = new Date();
@@ -268,7 +269,7 @@ export default function RecentlyDeletedScreen(): React.ReactNode {
* Render an item card.
*/
const renderItem = (item: ItemWithDeletedAt): React.ReactElement => {
const daysRemaining = item.DeletedAt ? getDaysRemaining(item.DeletedAt) : 30;
const daysRemaining = item.DeletedAt ? getDaysRemaining(item.DeletedAt) : TRASH_RETENTION_DAYS;
return (
<View key={item.Id} style={styles.itemCard}>
@@ -343,7 +344,7 @@ export default function RecentlyDeletedScreen(): React.ReactNode {
</TouchableOpacity>
</View>
<ThemedText style={styles.headerText}>
{t('items.recentlyDeleted.description')}
{t('items.recentlyDeleted.description', { days: TRASH_RETENTION_DAYS })}
</ThemedText>
{items.map(renderItem)}
</>
@@ -353,7 +354,7 @@ export default function RecentlyDeletedScreen(): React.ReactNode {
{t('items.recentlyDeleted.noItems')}
</ThemedText>
<ThemedText style={styles.emptyDescription}>
{t('items.recentlyDeleted.noItemsDescription')}
{t('items.recentlyDeleted.noItemsDescription', { days: TRASH_RETENTION_DAYS })}
</ThemedText>
</View>
)}

View File

@@ -0,0 +1,6 @@
/**
* Number of days that soft-deleted items stay in the "Recently Deleted" trash
* before being permanently pruned during vault sync. Mirrors the server-side
* default exposed via Config.TrashRetentionDays in the Blazor client.
*/
export const TRASH_RETENTION_DAYS = 30;

View File

@@ -518,8 +518,8 @@
"recentlyDeleted": {
"title": "Recently Deleted",
"noItems": "No deleted items",
"noItemsDescription": "Items you delete will appear here for 30 days before being permanently removed.",
"description": "These items will be permanently deleted after 30 days. You can restore them or delete them immediately.",
"noItemsDescription": "Items you delete will appear here for {{days}} days before being permanently removed.",
"description": "These items will be permanently deleted after {{days}} days. You can restore them or delete them immediately.",
"restore": "Restore",
"deletePermanently": "Delete Permanently",
"emptyAll": "Empty All",