feat(web): add account workspaces and signed content flows

This commit is contained in:
justme committed 2026-10-14 04:06:03 +08:00
1 parent 7b8d61d1ba
commit b5cf3d5958
117 files changed
+2789 -1674

No files matched your search

@@ -1,10 +1,10 @@
import { describe, expect, it } from 'vitest';
import { exportResource, mergeVault, validatePersonal, type VaultProfile } from './accountVault';
import { exportResource, mergeVault, type VaultProfile } from './accountVault';
import type { SavedResource } from '../database';
const resource = (modifiedAt = 10): SavedResource => ({ id: 'AAAAAAAAAAAAAAAAAAAAAA', kind: 'channel', mode: 'enc', role: 'owner',
link: 'https://drop.test/c/a#k=secret', endpointSecret: new Uint8Array(32).fill(7), deviceLabel: 'device-a', localScope: 'account:alice',
createdAt: 1, modifiedAt, expiresAt: 100, seats: 3, membersCanUpload: true, name: `version-${modifiedAt}` });
const profile = (): VaultProfile => ({ v: 2, keys: { publicKey: {}, privateKey: {} }, resources: [exportResource(resource(20))], deletes: [] });
createdAt: 1, modifiedAt, expiresAt: 100, seats: 3, name: `version-${modifiedAt}` });
const profile = (): VaultProfile => ({ v: 3, keys: { publicKey: {}, privateKey: {}, signing: { publicKey: {}, privateKey: {} } }, resources: [exportResource(resource(20))], deletes: [] });
describe('account vault portability and merge', () => {
it('never synchronizes device identity or local scope', () => {
const exported = exportResource(resource());
@@ -24,12 +24,5 @@ describe('account vault portability and merge', () => {
const merged = mergeVault(remote, [resource(40)], []);
expect(merged.resources).toHaveLength(1); expect(merged.deletes).toEqual([]);
});
it('keeps the newest personal profile across stale devices, including avatar removal', () => {
const remote = { ...profile(), personal: { displayName: '最新名称', avatar: 'data:image/png;base64,AA==', modifiedAt: 20 } };
expect(mergeVault(remote, [], [], { displayName: '旧名称', modifiedAt: 10 }).personal).toEqual(remote.personal);
expect(mergeVault(remote, [], [], { displayName: '新名称', modifiedAt: 30 }).personal).not.toHaveProperty('avatar');
});
it('rejects remote executable and external avatars', () => {
for (const avatar of ['https://tracker.test/avatar.png', 'data:image/svg+xml;base64,AA==']) expect(() => validatePersonal({ displayName: 'Alice', avatar, modifiedAt: 1 })).toThrow();
});
});