feat: 发布 v0.5.0 加密存储与匿名同步
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
'use client';
|
||||
|
||||
import { AssessmentRecord, EncryptedAssessmentRecord } from '@/lib/assessment-types';
|
||||
import {
|
||||
decryptAnonymousRecord,
|
||||
encryptRecordForAnonymousProfile,
|
||||
} from '@/lib/record-crypto';
|
||||
|
||||
const SESSION_KEY = 'mindscope_anonymous_session';
|
||||
|
||||
export interface AnonymousSession {
|
||||
codeName: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
export interface AnonymousProfile {
|
||||
id: string;
|
||||
codeName: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
lastSeenAt: string;
|
||||
}
|
||||
|
||||
interface AnonymousEncryptedLoginResult {
|
||||
profile: AnonymousProfile;
|
||||
records: EncryptedAssessmentRecord[];
|
||||
}
|
||||
|
||||
export interface AnonymousLoginResult {
|
||||
profile: AnonymousProfile;
|
||||
records: AssessmentRecord[];
|
||||
}
|
||||
|
||||
async function parseResponse<T>(response: Response): Promise<T> {
|
||||
const data = await response.json().catch(() => ({}));
|
||||
if (!response.ok) {
|
||||
throw new Error(typeof data.error === 'string' ? data.error : '请求失败');
|
||||
}
|
||||
return data as T;
|
||||
}
|
||||
|
||||
export function getAnonymousSession(): AnonymousSession | null {
|
||||
try {
|
||||
const raw = sessionStorage.getItem(SESSION_KEY);
|
||||
if (!raw) return null;
|
||||
const parsed = JSON.parse(raw) as Partial<AnonymousSession>;
|
||||
return parsed.codeName && parsed.password
|
||||
? { codeName: parsed.codeName, password: parsed.password }
|
||||
: null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function saveAnonymousSession(session: AnonymousSession) {
|
||||
sessionStorage.setItem(SESSION_KEY, JSON.stringify(session));
|
||||
}
|
||||
|
||||
export function clearAnonymousSession() {
|
||||
sessionStorage.removeItem(SESSION_KEY);
|
||||
}
|
||||
|
||||
async function decryptLoginResult(
|
||||
encryptedResult: AnonymousEncryptedLoginResult,
|
||||
session: AnonymousSession,
|
||||
): Promise<AnonymousLoginResult> {
|
||||
const records = await Promise.all(
|
||||
encryptedResult.records.map((record) => decryptAnonymousRecord(record, session.codeName, session.password)),
|
||||
);
|
||||
return {
|
||||
profile: encryptedResult.profile,
|
||||
records: records.sort((a, b) => b.completedAt.localeCompare(a.completedAt)),
|
||||
};
|
||||
}
|
||||
|
||||
export async function loginAnonymousProfile(session: AnonymousSession) {
|
||||
const encryptedResult = await fetch('/api/anonymous/profile', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(session),
|
||||
}).then((response) => parseResponse<AnonymousEncryptedLoginResult>(response));
|
||||
saveAnonymousSession(session);
|
||||
return decryptLoginResult(encryptedResult, session);
|
||||
}
|
||||
|
||||
export async function syncAnonymousRecord(record: AssessmentRecord, session = getAnonymousSession()) {
|
||||
if (!session) return null;
|
||||
const encryptedRecord = await encryptRecordForAnonymousProfile(record, session.codeName, session.password);
|
||||
const result = await fetch('/api/anonymous/records', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ ...session, encryptedRecord }),
|
||||
}).then((response) => parseResponse<{ record: EncryptedAssessmentRecord }>(response));
|
||||
return decryptAnonymousRecord(result.record, session.codeName, session.password);
|
||||
}
|
||||
|
||||
export async function deleteAnonymousProfile(session: AnonymousSession) {
|
||||
const result = await fetch('/api/anonymous/profile', {
|
||||
method: 'DELETE',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(session),
|
||||
}).then((response) => parseResponse<{ ok: boolean }>(response));
|
||||
clearAnonymousSession();
|
||||
return result;
|
||||
}
|
||||
Reference in New Issue
Block a user