feat(web): add account workspaces and signed content flows
This commit is contained in:
1 parent
7b8d61d1ba
commit
b5cf3d5958
117 files changed
+2789
-1674
No files matched your search
@@ -1,18 +1,18 @@
|
||||
import { ChannelSession } from '@webdrop/core';
|
||||
import { getResource, importResources, listAllResources, listResourceDeletes, type SavedResource } from '../database';
|
||||
import { openChannelGrant, sealChannelGrant } from './channelGrant';
|
||||
import { getResource, importResources, listResourceDeletes, type SavedResource } from '../database';
|
||||
import { openChannelGrant } from './channelGrant';
|
||||
import { makeLink } from './links';
|
||||
import { apiFetch, readJson } from './http';
|
||||
import type { InvitationKeyPair } from './accountCrypto';
|
||||
import type { ChannelWire } from './wire';
|
||||
|
||||
interface AccountChannel { channel_id: string; role: 'owner' | 'member'; payload: string; created_at: number }
|
||||
import type { AccountChannelBody } from '../generated/api';
|
||||
import { channelCache } from './resourceMetadata';
|
||||
|
||||
export async function recoverAccountChannels(username: string, keys: InvitationKeyPair, signal: AbortSignal): Promise<void> {
|
||||
const grants = await apiFetch('/accounts/channels', { signal }).then((r) => r.json() as Promise<AccountChannel[]>);
|
||||
const grants = await apiFetch('/accounts/channels', { signal }).then((r) => r.json() as Promise<AccountChannelBody[]>);
|
||||
const deletes = new Map((await listResourceDeletes()).map((row) => [row.id, row.modifiedAt]));
|
||||
const known = new Set(grants.map((row) => row.channel_id));
|
||||
for (const grant of grants) {
|
||||
if (grant.role !== 'owner' && grant.role !== 'member') throw new Error('空间成员权限无效');
|
||||
if (signal.aborted) throw new DOMException('账号已切换', 'AbortError');
|
||||
if ((deletes.get(grant.channel_id) ?? -1) >= grant.created_at || await getResource(grant.channel_id)) continue;
|
||||
const parsed = await openChannelGrant(grant.payload, username, grant.channel_id, keys.privateKey, grant.role, self.location.origin);
|
||||
@@ -21,25 +21,13 @@ export async function recoverAccountChannels(username: string, keys: InvitationK
|
||||
const info = await readJson<ChannelWire>(`/channels/${session.id}`, session.authHeaders('read'));
|
||||
session.verifyChannelInfo(info);
|
||||
if (grant.role === 'owner' && info.creator_account !== username) throw new Error('空间所属账号不匹配');
|
||||
const resource: SavedResource = { id: session.id, kind: 'channel', mode: session.mode, role: grant.role,
|
||||
creatorAccount: info.creator_account, name: info.settings.name,
|
||||
const resource: SavedResource = { ...channelCache(info), id: session.id, kind: 'channel', mode: session.mode, role: grant.role,
|
||||
creatorAccount: info.creator_account ?? undefined, name: info.settings.name ?? undefined,
|
||||
link: session.link(self.location.origin), ...(grant.role === 'owner' ? { ownerLink: session.link(self.location.origin, 'owner') } : {}),
|
||||
endpointSecret: new Uint8Array(32), createdAt: info.created_at, modifiedAt: grant.created_at, expiresAt: info.expires_at,
|
||||
seats: info.settings.seats, membersCanUpload: info.settings.members_can_upload, maxDownloads: info.settings.file_max_downloads };
|
||||
seats: info.settings.seats, maxDownloads: info.settings.file_max_downloads };
|
||||
if (signal.aborted) throw new DOMException('账号已切换', 'AbortError');
|
||||
await importResources([resource]);
|
||||
} finally { session.dispose(); }
|
||||
}
|
||||
// Populate owner grants for channels created before account-bound delivery existed.
|
||||
for (const resource of await listAllResources()) {
|
||||
if (resource.kind !== 'channel' || resource.role !== 'owner' || !resource.ownerLink || known.has(resource.id)) continue;
|
||||
const session = await ChannelSession.open(resource.ownerLink, { endpointSecret: resource.endpointSecret });
|
||||
try {
|
||||
const payload = await sealChannelGrant(resource.ownerLink, username, keys.publicKey);
|
||||
await apiFetch(`/channels/${resource.id}/key-package`, { method: 'PUT', signal,
|
||||
headers: { ...session.authHeaders('owner'), 'Content-Type': 'application/json' }, body: JSON.stringify({ payload }) });
|
||||
} catch (error) {
|
||||
if (!(error instanceof Error && 'status' in error && error.status === 404)) throw error;
|
||||
} finally { session.dispose(); }
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user