feat: 发布 v0.5.0 加密存储与匿名同步

This commit is contained in:
2026-06-23 01:48:01 +02:00
parent 81a70137a9
commit e3825c5a4e
20 changed files with 1091 additions and 70 deletions
+34 -6
View File
@@ -1,8 +1,15 @@
import {
AssessmentProfile,
AssessmentRecord,
EncryptedAssessmentRecord,
MindScopeBackup,
} from '@/lib/assessment-types';
import {
clearDeviceRecordKey,
decryptDeviceRecord,
encryptRecordForDevice,
isEncryptedRecord,
} from '@/lib/record-crypto';
const DB_NAME = 'mindscope';
const DB_VERSION = 1;
@@ -87,7 +94,7 @@ export async function ensureActiveProfile(): Promise<AssessmentProfile> {
export async function renameProfile(profileId: string, name: string) {
const db = await openDatabase();
const store = db.transaction(PROFILE_STORE, 'readwrite').objectStore(PROFILE_STORE);
const profile = await requestResult(store.get(profileId));
const profile = await requestResult<AssessmentProfile | undefined>(store.get(profileId));
if (profile) {
await requestResult(
store.put({ ...profile, name: name.trim(), updatedAt: new Date().toISOString() }),
@@ -114,9 +121,10 @@ export async function addAssessmentRecord(
record: Omit<AssessmentRecord, 'id'>,
): Promise<AssessmentRecord> {
const completeRecord = { ...record, id: createId('record') };
const encrypted = await encryptRecordForDevice(completeRecord);
const db = await openDatabase();
await requestResult(
db.transaction(RECORD_STORE, 'readwrite').objectStore(RECORD_STORE).add(completeRecord),
db.transaction(RECORD_STORE, 'readwrite').objectStore(RECORD_STORE).add(encrypted),
);
db.close();
return completeRecord;
@@ -125,9 +133,10 @@ export async function addAssessmentRecord(
export async function updateRecordAnalysis(recordId: string, analysisText: string) {
const db = await openDatabase();
const store = db.transaction(RECORD_STORE, 'readwrite').objectStore(RECORD_STORE);
const record = await requestResult(store.get(recordId));
const stored = await requestResult<AssessmentRecord | EncryptedAssessmentRecord | undefined>(store.get(recordId));
const record = stored ? await decryptDeviceRecord(stored) : undefined;
if (record && record.analysisText !== analysisText) {
await requestResult(store.put({ ...record, analysisText }));
await requestResult(store.put(await encryptRecordForDevice({ ...record, analysisText })));
}
db.close();
}
@@ -135,13 +144,29 @@ export async function updateRecordAnalysis(recordId: string, analysisText: strin
export async function getRecords(profileId?: string): Promise<AssessmentRecord[]> {
const db = await openDatabase();
const store = db.transaction(RECORD_STORE, 'readonly').objectStore(RECORD_STORE);
const records = profileId
const storedRecords = profileId
? await requestResult(store.index('profileId').getAll(profileId))
: await requestResult(store.getAll());
db.close();
const typedRecords = storedRecords as Array<AssessmentRecord | EncryptedAssessmentRecord>;
const records = await Promise.all(typedRecords.map(decryptDeviceRecord));
const plaintextRecords = typedRecords.filter((record) => !isEncryptedRecord(record)) as AssessmentRecord[];
if (plaintextRecords.length) {
await migratePlaintextRecords(plaintextRecords);
}
return records.sort((a, b) => b.completedAt.localeCompare(a.completedAt));
}
async function migratePlaintextRecords(records: AssessmentRecord[]) {
const db = await openDatabase();
const store = db.transaction(RECORD_STORE, 'readwrite').objectStore(RECORD_STORE);
for (const record of records) {
await requestResult(store.put(await encryptRecordForDevice(record)));
}
db.close();
}
export async function deleteRecord(recordId: string) {
const db = await openDatabase();
await requestResult(
@@ -167,7 +192,9 @@ export async function importBackup(backup: MindScopeBackup) {
const db = await openDatabase();
const transaction = db.transaction([PROFILE_STORE, RECORD_STORE], 'readwrite');
backup.profiles.forEach((profile) => transaction.objectStore(PROFILE_STORE).put(profile));
backup.records.forEach((record) => transaction.objectStore(RECORD_STORE).put(record));
for (const record of backup.records) {
transaction.objectStore(RECORD_STORE).put(await encryptRecordForDevice(record));
}
await new Promise<void>((resolve, reject) => {
transaction.oncomplete = () => resolve();
transaction.onerror = () => reject(transaction.error);
@@ -186,4 +213,5 @@ export async function clearAllAssessmentData() {
});
db.close();
localStorage.removeItem(ACTIVE_PROFILE_KEY);
clearDeviceRecordKey();
}