mirror of
https://github.com/xtr-dev/rondevu-client.git
synced 2025-12-10 10:53:24 +00:00
Added comprehensive API client and signaling implementation: **RondevuAPI** - Single class for all Rondevu endpoints: - Authentication: register() - Offers: createOffers(), getOffer(), answerOffer(), getAnswer(), searchOffers() - ICE Candidates: addIceCandidates(), getIceCandidates() - Services: publishService(), getService(), searchServices() - Usernames: checkUsername(), claimUsername() **RondevuSignaler** - ICE candidate exchange: - addIceCandidate() - Send local candidates to server - addListener() - Poll for remote candidates (1 second intervals) - Returns cleanup function (Binnable) to stop polling - Handles offer expiration gracefully **WebRTCRondevuConnection** - WebRTC connection wrapper: - Handles offer/answer creation - Manages ICE candidate exchange via Signaler - Type-safe event bus for state changes and messages - Queue and send message interfaces **Utilities**: - createBin() - Cleanup function collector - Binnable type - Cleanup function signature All classes use the shared RondevuAPI client for consistent error handling and authentication. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
76 lines
2.7 KiB
TypeScript
76 lines
2.7 KiB
TypeScript
import {ConnectionEvents, ConnectionInterface, Message, QueueMessageOptions, Signaler} from "./types";
|
|
import {EventBus} from "./event-bus";
|
|
import {createBin} from "./bin";
|
|
|
|
export class WebRTCRondevuConnection implements ConnectionInterface {
|
|
private readonly connection: RTCPeerConnection;
|
|
private readonly side: 'offer' | 'answer';
|
|
public readonly expiresAt: number = 0;
|
|
public readonly lastActive: number = 0;
|
|
public readonly events: EventBus<ConnectionEvents> = new EventBus();
|
|
private signaler!: Signaler; // Will be set by setSignaler()
|
|
private readonly _ready: Promise<void>;
|
|
private _state: ConnectionInterface['state'] = 'disconnected';
|
|
private iceBin = createBin()
|
|
|
|
constructor(
|
|
public readonly id: string,
|
|
public readonly host: string,
|
|
public readonly service: string,
|
|
offer?: RTCSessionDescriptionInit) {
|
|
this.connection = new RTCPeerConnection();
|
|
this.side = offer ? 'answer' : 'offer';
|
|
const ready = offer
|
|
? this.connection.setRemoteDescription(offer)
|
|
.then(() => this.connection.createAnswer())
|
|
.then(answer => this.connection.setLocalDescription(answer))
|
|
: this.connection.createOffer()
|
|
.then(offer => this.connection.setLocalDescription(offer));
|
|
this._ready = ready.then(() => this.setState('connecting'))
|
|
.then(() => this.startIceListeners())
|
|
}
|
|
|
|
private setState(state: ConnectionInterface['state']) {
|
|
this._state = state;
|
|
this.events.emit('state-change', state);
|
|
}
|
|
|
|
private startIceListeners() {
|
|
const listener = ({candidate}: {candidate: RTCIceCandidate | null}) => {
|
|
if (candidate) this.signaler.addIceCandidate(candidate)
|
|
}
|
|
this.connection.addEventListener('icecandidate', listener)
|
|
this.iceBin(
|
|
this.signaler.addListener((candidate: RTCIceCandidate) => this.connection.addIceCandidate(candidate)),
|
|
() => this.connection.removeEventListener('icecandidate', listener)
|
|
)
|
|
}
|
|
|
|
private stopIceListeners() {
|
|
this.iceBin.clean()
|
|
}
|
|
|
|
/**
|
|
* Set the signaler for ICE candidate exchange
|
|
* Must be called before connection is ready
|
|
*/
|
|
setSignaler(signaler: Signaler): void {
|
|
this.signaler = signaler;
|
|
}
|
|
|
|
get state() {
|
|
return this._state;
|
|
}
|
|
|
|
get ready(): Promise<void> {
|
|
return this._ready;
|
|
}
|
|
|
|
queueMessage(message: Message, options: QueueMessageOptions = {}): Promise<void> {
|
|
return Promise.resolve(undefined);
|
|
}
|
|
|
|
sendMessage(message: Message): Promise<boolean> {
|
|
return Promise.resolve(false);
|
|
}
|
|
} |