refactor(frontend): extract decryptPrivateCleartext helper
Home.svelte and TagPage.svelte both pre-decrypted private rows with the same ~15-line $derived.by block (loop + decryptPayload + try/catch). Move it into a small lib helper so future changes (parallelism, error reporting) live in one place. Both call sites now use a plain $derived(decryptPrivateCleartext(activities, session.dek)). Surfaced by /audit simplify. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
03ac99e555
commit
45ad3ea3bb
3 changed files with 43 additions and 38 deletions
|
|
@ -3,9 +3,7 @@
|
|||
import { dndzone, SHADOW_ITEM_MARKER_PROPERTY_NAME } from 'svelte-dnd-action';
|
||||
import { api } from '../lib/api';
|
||||
import { session } from '../lib/session.svelte';
|
||||
import {
|
||||
decryptPayload, base64ToBytes, type PrivatePayload,
|
||||
} from '../lib/crypto';
|
||||
import { decryptPrivateCleartext } from '../lib/privateCleartext';
|
||||
import ActivityForm from './ActivityForm.svelte';
|
||||
import ActivityRow from './ActivityRow.svelte';
|
||||
import type { Activity } from '../../../shared/types';
|
||||
|
|
@ -56,20 +54,7 @@
|
|||
}
|
||||
|
||||
/** Pre-decrypt private payloads once per render so search can match. */
|
||||
const privateCleartext = $derived.by(() => {
|
||||
const map = new Map<string, PrivatePayload>();
|
||||
if (!session.dek) return map;
|
||||
for (const a of activities) {
|
||||
if (a.visibility !== 'private') continue;
|
||||
try {
|
||||
map.set(a.id, decryptPayload(
|
||||
{ ciphertext: base64ToBytes(a.ciphertext), nonce: base64ToBytes(a.nonce) },
|
||||
session.dek,
|
||||
));
|
||||
} catch { /* skip */ }
|
||||
}
|
||||
return map;
|
||||
});
|
||||
const privateCleartext = $derived(decryptPrivateCleartext(activities, session.dek));
|
||||
|
||||
function matchesQuery(a: Activity, q: string): boolean {
|
||||
if (!q) return true;
|
||||
|
|
|
|||
|
|
@ -2,9 +2,7 @@
|
|||
import { onMount } from 'svelte';
|
||||
import { api } from '../lib/api';
|
||||
import { session } from '../lib/session.svelte';
|
||||
import {
|
||||
decryptPayload, base64ToBytes, type PrivatePayload,
|
||||
} from '../lib/crypto';
|
||||
import { decryptPrivateCleartext } from '../lib/privateCleartext';
|
||||
import ActivityRow from './ActivityRow.svelte';
|
||||
import type { Activity } from '../../../shared/types';
|
||||
|
||||
|
|
@ -37,24 +35,9 @@
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pre-decrypt private payloads once so we can both filter by tag and let
|
||||
* ActivityRow render without re-doing the work. Same pattern as Home.svelte.
|
||||
*/
|
||||
const privateCleartext = $derived.by(() => {
|
||||
const map = new Map<string, PrivatePayload>();
|
||||
if (!session.dek) return map;
|
||||
for (const a of activities) {
|
||||
if (a.visibility !== 'private') continue;
|
||||
try {
|
||||
map.set(a.id, decryptPayload(
|
||||
{ ciphertext: base64ToBytes(a.ciphertext), nonce: base64ToBytes(a.nonce) },
|
||||
session.dek,
|
||||
));
|
||||
} catch { /* skip undecryptable rows */ }
|
||||
}
|
||||
return map;
|
||||
});
|
||||
/** Pre-decrypt private rows once so we can filter by tag and let
|
||||
* ActivityRow render without redoing the work. */
|
||||
const privateCleartext = $derived(decryptPrivateCleartext(activities, session.dek));
|
||||
|
||||
function hasTag(a: Activity): boolean {
|
||||
if (a.visibility === 'private') {
|
||||
|
|
|
|||
37
frontend/src/lib/privateCleartext.ts
Normal file
37
frontend/src/lib/privateCleartext.ts
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
/**
|
||||
* Bulk-decrypt the private rows in a list of activities, returning a
|
||||
* Map<id, PrivatePayload>. Rows that fail to decrypt (corrupted ciphertext,
|
||||
* wrong DEK) are silently skipped — the caller treats their absence as
|
||||
* "no plaintext available" rather than an error, matching how the UI
|
||||
* already displays undecryptable rows.
|
||||
*
|
||||
* Originally duplicated in Home.svelte and TagPage.svelte; consolidated
|
||||
* here so any future change (parallelism, telemetry on decrypt failures)
|
||||
* lives in one place.
|
||||
*/
|
||||
import { decryptPayload, base64ToBytes, type PrivatePayload } from './crypto';
|
||||
import type { Activity } from '../../../shared/types';
|
||||
|
||||
export function decryptPrivateCleartext(
|
||||
activities: Activity[],
|
||||
dek: Uint8Array | null,
|
||||
): Map<string, PrivatePayload> {
|
||||
const map = new Map<string, PrivatePayload>();
|
||||
if (!dek) return map;
|
||||
for (const a of activities) {
|
||||
if (a.visibility !== 'private') continue;
|
||||
try {
|
||||
map.set(
|
||||
a.id,
|
||||
decryptPayload(
|
||||
{ ciphertext: base64ToBytes(a.ciphertext), nonce: base64ToBytes(a.nonce) },
|
||||
dek,
|
||||
),
|
||||
);
|
||||
} catch {
|
||||
// Skip rows we can't decrypt — UI shows them as "log in again" via
|
||||
// the !session.dek branch in ActivityRow.
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue