feat: integrate client and server sharing workflows

This commit is contained in:
justme committed 2026-10-02 10:49:45 +08:00
1 parent 42b93ae2d8
commit 7406e2fe37
101 files changed
+3153 -253

No files matched your search

@@ -0,0 +1,95 @@
import { ChannelSession } from '@webdrop/core';
import { getResource, listResources, removeResource, saveResource, type SavedResource } from '../database';
import type { ParsedChannelLink } from '@/lib/channelLink';
import type { ChannelInfo, CreateOptions } from '../types';
import { apiFetch, readJson } from './http';
import { sessions } from './sessions';
import { deviceIdentity } from './presentation';
import { makeLink } from './links';
import { toChannelInfo, type ChannelWire } from './wire';
import { stopChannel } from './channelTransport';
export async function createChannel(options: CreateOptions, captchaToken?: string): Promise<ChannelInfo> {
const endpointSecret = crypto.getRandomValues(new Uint8Array(32));
const { session, request } = await ChannelSession.create({
mode: options.mode,
endpointSecret,
settings: {
seats: options.seats,
ttl_seconds: options.ttlSeconds,
members_can_upload: options.membersCanUpload,
delete_policy: 'own',
max_items: 1000,
file_max_downloads: 10,
ui_restrict_copy_download: false,
},
});
try {
const info = await apiFetch('/channels', {
method: 'POST', headers: { ...request.headers, 'Content-Type': 'application/json', ...(captchaToken ? { 'X-WD-Cap': captchaToken } : {}) }, body: JSON.stringify(request.body),
}).then((response) => response.json() as Promise<ChannelWire>);
session.verifyChannelInfo(info);
const resource: SavedResource = {
id: session.id,
kind: 'channel', mode: session.mode, role: 'owner',
link: session.link(self.location.origin), ownerLink: session.link(self.location.origin, 'owner'),
endpointSecret: Uint8Array.from(session.endpointSecret), createdAt: info.created_at, expiresAt: info.expires_at,
seats: info.settings.seats, membersCanUpload: info.settings.members_can_upload,
maxDownloads: info.settings.file_max_downloads, localItems: [],
};
await saveResource(resource);
sessions.set(session.id, session);
return toChannelInfo(info, resource);
} catch (error) {
session.dispose();
throw error;
}
}
export async function joinChannel(input: { link: ParsedChannelLink }): Promise<ChannelInfo> {
const { id, keys } = input.link;
const existing = await getResource(id);
const endpointSecret = existing?.endpointSecret ?? crypto.getRandomValues(new Uint8Array(32));
const session = await ChannelSession.open(makeLink(self.location.origin, id, keys), { endpointSecret });
try {
const info = await readJson<ChannelWire>(`/channels/${id}`, session.authHeaders('read'));
session.verifyChannelInfo(info);
await apiFetch(`/channels/${id}/seats`, { method: 'POST', headers: session.authHeaders(session.can('owner') ? 'owner' : 'read') });
const resource: SavedResource = {
id, kind: 'channel', mode: session.mode, role: session.role,
link: session.link(self.location.origin), ownerLink: session.can('owner') ? session.link(self.location.origin, 'owner') : undefined,
endpointSecret: Uint8Array.from(session.endpointSecret), createdAt: info.created_at, expiresAt: info.expires_at,
seats: info.settings.seats, membersCanUpload: info.settings.members_can_upload,
maxDownloads: info.settings.file_max_downloads, localItems: existing?.localItems ?? [],
};
const label = await session.sealLabel(deviceIdentity().name);
await apiFetch(`/channels/${id}/seats/me`, {
method: 'PUT', headers: { ...session.authHeaders('read'), 'Content-Type': 'application/octet-stream' }, body: label,
});
await saveResource(resource);
sessions.set(id, session);
return toChannelInfo(info, resource);
} catch (error) {
session.dispose();
throw error;
}
}
export async function listChannels(): Promise<ChannelInfo[]> {
const saved = await listResources('channel');
return saved.map((resource) => ({
id: resource.id, mode: resource.mode, role: resource.role, seats: resource.seats,
memberLink: resource.link, ownerLink: resource.ownerLink, createdAt: resource.createdAt, expiresAt: resource.expiresAt,
}));
}
export async function forgetChannel(id: string): Promise<void> {
stopChannel(id);
sessions.get(id)?.dispose();
sessions.delete(id);
await removeResource(id);
}