init: FileDrop phase1 architecture and scaffold

- Rust axum signaling server with WebSocket support
- Lit + TypeScript frontend with Vite
- Redis session storage with TTL
- WebRTC transport and crypto client stubs
- Phase1 architecture plan in plans/
- Deploy directory structure prepared
This commit is contained in:
2026-04-09 10:32:06 +08:00
commit 4b34a85599
34 changed files with 1561 additions and 0 deletions

View File

@@ -0,0 +1,70 @@
export class WebRTCTransport {
private pc: RTCPeerConnection | null = null;
private dc: RTCDataChannel | null = null;
private iceServers: RTCIceServer[];
constructor(iceServers: RTCIceServer[]) {
this.iceServers = iceServers;
}
async createOffer(): Promise<{ sdp: string; candidates: RTCIceCandidate[] }> {
this.pc = new RTCPeerConnection({ iceServers: this.iceServers });
this.dc = this.pc.createDataChannel('filedrop', { ordered: true });
const offer = await this.pc.createOffer();
await this.pc.setLocalDescription(offer);
const candidates = await this.waitForIceGathering();
return {
sdp: this.pc.localDescription?.sdp || '',
candidates,
};
}
async handleAnswer(answer: { sdp: string }) {
if (!this.pc) return;
await this.pc.setRemoteDescription(
new RTCSessionDescription({ type: 'answer', sdp: answer.sdp })
);
}
async handleCandidate(candidate: RTCIceCandidateInit) {
await this.pc?.addIceCandidate(candidate);
}
onDataChannel(callback: (dc: RTCDataChannel) => void) {
if (this.pc) {
this.pc.ondatachannel = (e) => callback(e.channel);
}
}
getDataChannel() {
return this.dc;
}
private waitForIceGathering(): Promise<RTCIceCandidate[]> {
return new Promise((resolve) => {
const candidates: RTCIceCandidate[] = [];
if (!this.pc) return resolve(candidates);
this.pc.onicecandidate = (e) => {
if (e.candidate) candidates.push(e.candidate);
};
this.pc.onicegatheringstatechange = () => {
if (this.pc?.iceGatheringState === 'complete') {
resolve(candidates);
}
};
setTimeout(() => resolve(candidates), 5000);
});
}
close() {
this.dc?.close();
this.pc?.close();
this.dc = null;
this.pc = null;
}
}