mirror of
https://github.com/xtr-dev/rondevu-server.git
synced 2025-12-11 03:13:26 +00:00
Refactor: Store ICE candidates as JSON objects
- Simplify storage by storing entire RTCIceCandidateInit as JSON - Remove individual sdp_mid and sdp_m_line_index columns - More future-proof and maintainable approach - Recreated ice_candidates table in D1 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import { Storage, Offer, IceCandidate, CreateOfferRequest, TopicInfo } from './types.ts';
|
import { Storage, Offer, IceCandidate, CreateOfferRequest, TopicInfo, RTCIceCandidateInit } from './types.ts';
|
||||||
|
|
||||||
// Generate a UUID v4
|
// Generate a UUID v4
|
||||||
function generateUUID(): string {
|
function generateUUID(): string {
|
||||||
@@ -58,9 +58,7 @@ export class D1Storage implements Storage {
|
|||||||
offer_id TEXT NOT NULL,
|
offer_id TEXT NOT NULL,
|
||||||
peer_id TEXT NOT NULL,
|
peer_id TEXT NOT NULL,
|
||||||
role TEXT NOT NULL CHECK(role IN ('offerer', 'answerer')),
|
role TEXT NOT NULL CHECK(role IN ('offerer', 'answerer')),
|
||||||
candidate TEXT NOT NULL,
|
candidate TEXT NOT NULL, -- JSON: RTCIceCandidateInit object
|
||||||
sdp_mid TEXT,
|
|
||||||
sdp_m_line_index INTEGER,
|
|
||||||
created_at INTEGER NOT NULL,
|
created_at INTEGER NOT NULL,
|
||||||
FOREIGN KEY (offer_id) REFERENCES offers(id) ON DELETE CASCADE
|
FOREIGN KEY (offer_id) REFERENCES offers(id) ON DELETE CASCADE
|
||||||
);
|
);
|
||||||
@@ -244,24 +242,18 @@ export class D1Storage implements Storage {
|
|||||||
offerId: string,
|
offerId: string,
|
||||||
peerId: string,
|
peerId: string,
|
||||||
role: 'offerer' | 'answerer',
|
role: 'offerer' | 'answerer',
|
||||||
candidates: Array<{
|
candidates: RTCIceCandidateInit[]
|
||||||
candidate: string;
|
|
||||||
sdpMid?: string | null;
|
|
||||||
sdpMLineIndex?: number | null;
|
|
||||||
}>
|
|
||||||
): Promise<number> {
|
): Promise<number> {
|
||||||
// D1 doesn't have transactions, so insert one by one
|
// D1 doesn't have transactions, so insert one by one
|
||||||
for (const cand of candidates) {
|
for (const cand of candidates) {
|
||||||
await this.db.prepare(`
|
await this.db.prepare(`
|
||||||
INSERT INTO ice_candidates (offer_id, peer_id, role, candidate, sdp_mid, sdp_m_line_index, created_at)
|
INSERT INTO ice_candidates (offer_id, peer_id, role, candidate, created_at)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?)
|
||||||
`).bind(
|
`).bind(
|
||||||
offerId,
|
offerId,
|
||||||
peerId,
|
peerId,
|
||||||
role,
|
role,
|
||||||
cand.candidate,
|
JSON.stringify(cand), // Store full object as JSON
|
||||||
cand.sdpMid ?? null,
|
|
||||||
cand.sdpMLineIndex ?? null,
|
|
||||||
Date.now()
|
Date.now()
|
||||||
).run();
|
).run();
|
||||||
}
|
}
|
||||||
@@ -299,9 +291,7 @@ export class D1Storage implements Storage {
|
|||||||
offerId: row.offer_id,
|
offerId: row.offer_id,
|
||||||
peerId: row.peer_id,
|
peerId: row.peer_id,
|
||||||
role: row.role,
|
role: row.role,
|
||||||
candidate: row.candidate,
|
candidate: JSON.parse(row.candidate), // Parse JSON back to object
|
||||||
sdpMid: row.sdp_mid,
|
|
||||||
sdpMLineIndex: row.sdp_m_line_index,
|
|
||||||
createdAt: row.created_at,
|
createdAt: row.created_at,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import Database from 'better-sqlite3';
|
import Database from 'better-sqlite3';
|
||||||
import { randomUUID } from 'crypto';
|
import { randomUUID } from 'crypto';
|
||||||
import { Storage, Offer, IceCandidate, CreateOfferRequest, TopicInfo } from './types.ts';
|
import { Storage, Offer, IceCandidate, CreateOfferRequest, TopicInfo, RTCIceCandidateInit } from './types.ts';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SQLite storage adapter for topic-based offer management
|
* SQLite storage adapter for topic-based offer management
|
||||||
@@ -55,9 +55,7 @@ export class SQLiteStorage implements Storage {
|
|||||||
offer_id TEXT NOT NULL,
|
offer_id TEXT NOT NULL,
|
||||||
peer_id TEXT NOT NULL,
|
peer_id TEXT NOT NULL,
|
||||||
role TEXT NOT NULL CHECK(role IN ('offerer', 'answerer')),
|
role TEXT NOT NULL CHECK(role IN ('offerer', 'answerer')),
|
||||||
candidate TEXT NOT NULL,
|
candidate TEXT NOT NULL, -- JSON: RTCIceCandidateInit object
|
||||||
sdp_mid TEXT,
|
|
||||||
sdp_m_line_index INTEGER,
|
|
||||||
created_at INTEGER NOT NULL,
|
created_at INTEGER NOT NULL,
|
||||||
FOREIGN KEY (offer_id) REFERENCES offers(id) ON DELETE CASCADE
|
FOREIGN KEY (offer_id) REFERENCES offers(id) ON DELETE CASCADE
|
||||||
);
|
);
|
||||||
@@ -254,26 +252,20 @@ export class SQLiteStorage implements Storage {
|
|||||||
offerId: string,
|
offerId: string,
|
||||||
peerId: string,
|
peerId: string,
|
||||||
role: 'offerer' | 'answerer',
|
role: 'offerer' | 'answerer',
|
||||||
candidates: Array<{
|
candidates: RTCIceCandidateInit[]
|
||||||
candidate: string;
|
|
||||||
sdpMid?: string | null;
|
|
||||||
sdpMLineIndex?: number | null;
|
|
||||||
}>
|
|
||||||
): Promise<number> {
|
): Promise<number> {
|
||||||
const stmt = this.db.prepare(`
|
const stmt = this.db.prepare(`
|
||||||
INSERT INTO ice_candidates (offer_id, peer_id, role, candidate, sdp_mid, sdp_m_line_index, created_at)
|
INSERT INTO ice_candidates (offer_id, peer_id, role, candidate, created_at)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?)
|
||||||
`);
|
`);
|
||||||
|
|
||||||
const transaction = this.db.transaction((candidates: typeof candidates) => {
|
const transaction = this.db.transaction((candidates: RTCIceCandidateInit[]) => {
|
||||||
for (const cand of candidates) {
|
for (const cand of candidates) {
|
||||||
stmt.run(
|
stmt.run(
|
||||||
offerId,
|
offerId,
|
||||||
peerId,
|
peerId,
|
||||||
role,
|
role,
|
||||||
cand.candidate,
|
JSON.stringify(cand), // Store full object as JSON
|
||||||
cand.sdpMid ?? null,
|
|
||||||
cand.sdpMLineIndex ?? null,
|
|
||||||
Date.now()
|
Date.now()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -310,9 +302,7 @@ export class SQLiteStorage implements Storage {
|
|||||||
offerId: row.offer_id,
|
offerId: row.offer_id,
|
||||||
peerId: row.peer_id,
|
peerId: row.peer_id,
|
||||||
role: row.role,
|
role: row.role,
|
||||||
candidate: row.candidate,
|
candidate: JSON.parse(row.candidate), // Parse JSON back to object
|
||||||
sdpMid: row.sdp_mid,
|
|
||||||
sdpMLineIndex: row.sdp_m_line_index,
|
|
||||||
createdAt: row.created_at,
|
createdAt: row.created_at,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,18 +16,27 @@ export interface Offer {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents an ICE candidate for WebRTC signaling
|
* Represents an ICE candidate for WebRTC signaling
|
||||||
|
* Stores the complete RTCIceCandidateInit object
|
||||||
*/
|
*/
|
||||||
export interface IceCandidate {
|
export interface IceCandidate {
|
||||||
id: number;
|
id: number;
|
||||||
offerId: string;
|
offerId: string;
|
||||||
peerId: string;
|
peerId: string;
|
||||||
role: 'offerer' | 'answerer';
|
role: 'offerer' | 'answerer';
|
||||||
candidate: string;
|
candidate: RTCIceCandidateInit; // Full candidate object as JSON
|
||||||
sdpMid: string | null;
|
|
||||||
sdpMLineIndex: number | null;
|
|
||||||
createdAt: number;
|
createdAt: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RTCIceCandidateInit interface for TypeScript environments without WebRTC
|
||||||
|
*/
|
||||||
|
export interface RTCIceCandidateInit {
|
||||||
|
candidate?: string;
|
||||||
|
sdpMid?: string | null;
|
||||||
|
sdpMLineIndex?: number | null;
|
||||||
|
usernameFragment?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a topic with active peer count
|
* Represents a topic with active peer count
|
||||||
*/
|
*/
|
||||||
@@ -127,18 +136,14 @@ export interface Storage {
|
|||||||
* @param offerId Offer identifier
|
* @param offerId Offer identifier
|
||||||
* @param peerId Peer ID posting the candidates
|
* @param peerId Peer ID posting the candidates
|
||||||
* @param role Role of the peer (offerer or answerer)
|
* @param role Role of the peer (offerer or answerer)
|
||||||
* @param candidates Array of ICE candidate objects
|
* @param candidates Array of RTCIceCandidateInit objects
|
||||||
* @returns Number of candidates added
|
* @returns Number of candidates added
|
||||||
*/
|
*/
|
||||||
addIceCandidates(
|
addIceCandidates(
|
||||||
offerId: string,
|
offerId: string,
|
||||||
peerId: string,
|
peerId: string,
|
||||||
role: 'offerer' | 'answerer',
|
role: 'offerer' | 'answerer',
|
||||||
candidates: Array<{
|
candidates: RTCIceCandidateInit[]
|
||||||
candidate: string;
|
|
||||||
sdpMid?: string | null;
|
|
||||||
sdpMLineIndex?: number | null;
|
|
||||||
}>
|
|
||||||
): Promise<number>;
|
): Promise<number>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user