From b3bd2679fc49d6a5d7d40c83496cc93f9e39f7ad Mon Sep 17 00:00:00 2001 From: Bas van den Aakster Date: Fri, 14 Nov 2025 19:31:29 +0100 Subject: [PATCH] Fix ICE candidate handling - include sdpMid and sdpMLineIndex MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Update IceCandidate interface to include sdpMid and sdpMLineIndex fields - Update SQLite and D1 storage to store full ICE candidate data - Update server API to accept and return complete candidate objects - This fixes the 'Either sdpMid or sdpMLineIndex must be specified' error 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- src/storage/d1.ts | 26 +++++++++++++++++++++----- src/storage/sqlite.ts | 28 ++++++++++++++++++++++------ src/storage/types.ts | 10 ++++++++-- 3 files changed, 51 insertions(+), 13 deletions(-) diff --git a/src/storage/d1.ts b/src/storage/d1.ts index 7d50761..97a5f99 100644 --- a/src/storage/d1.ts +++ b/src/storage/d1.ts @@ -59,6 +59,8 @@ export class D1Storage implements Storage { peer_id TEXT NOT NULL, role TEXT NOT NULL CHECK(role IN ('offerer', 'answerer')), candidate TEXT NOT NULL, + sdp_mid TEXT, + sdp_m_line_index INTEGER, created_at INTEGER NOT NULL, FOREIGN KEY (offer_id) REFERENCES offers(id) ON DELETE CASCADE ); @@ -242,14 +244,26 @@ export class D1Storage implements Storage { offerId: string, peerId: string, role: 'offerer' | 'answerer', - candidates: string[] + candidates: Array<{ + candidate: string; + sdpMid?: string | null; + sdpMLineIndex?: number | null; + }> ): Promise { // D1 doesn't have transactions, so insert one by one - for (const candidate of candidates) { + for (const cand of candidates) { await this.db.prepare(` - INSERT INTO ice_candidates (offer_id, peer_id, role, candidate, created_at) - VALUES (?, ?, ?, ?, ?) - `).bind(offerId, peerId, role, candidate, Date.now()).run(); + INSERT INTO ice_candidates (offer_id, peer_id, role, candidate, sdp_mid, sdp_m_line_index, created_at) + VALUES (?, ?, ?, ?, ?, ?, ?) + `).bind( + offerId, + peerId, + role, + cand.candidate, + cand.sdpMid ?? null, + cand.sdpMLineIndex ?? null, + Date.now() + ).run(); } return candidates.length; @@ -286,6 +300,8 @@ export class D1Storage implements Storage { peerId: row.peer_id, role: row.role, candidate: row.candidate, + sdpMid: row.sdp_mid, + sdpMLineIndex: row.sdp_m_line_index, createdAt: row.created_at, })); } diff --git a/src/storage/sqlite.ts b/src/storage/sqlite.ts index a333038..87b20dd 100644 --- a/src/storage/sqlite.ts +++ b/src/storage/sqlite.ts @@ -56,6 +56,8 @@ export class SQLiteStorage implements Storage { peer_id TEXT NOT NULL, role TEXT NOT NULL CHECK(role IN ('offerer', 'answerer')), candidate TEXT NOT NULL, + sdp_mid TEXT, + sdp_m_line_index INTEGER, created_at INTEGER NOT NULL, FOREIGN KEY (offer_id) REFERENCES offers(id) ON DELETE CASCADE ); @@ -252,16 +254,28 @@ export class SQLiteStorage implements Storage { offerId: string, peerId: string, role: 'offerer' | 'answerer', - candidates: string[] + candidates: Array<{ + candidate: string; + sdpMid?: string | null; + sdpMLineIndex?: number | null; + }> ): Promise { const stmt = this.db.prepare(` - INSERT INTO ice_candidates (offer_id, peer_id, role, candidate, created_at) - VALUES (?, ?, ?, ?, ?) + INSERT INTO ice_candidates (offer_id, peer_id, role, candidate, sdp_mid, sdp_m_line_index, created_at) + VALUES (?, ?, ?, ?, ?, ?, ?) `); - const transaction = this.db.transaction((candidates: string[]) => { - for (const candidate of candidates) { - stmt.run(offerId, peerId, role, candidate, Date.now()); + const transaction = this.db.transaction((candidates: typeof candidates) => { + for (const cand of candidates) { + stmt.run( + offerId, + peerId, + role, + cand.candidate, + cand.sdpMid ?? null, + cand.sdpMLineIndex ?? null, + Date.now() + ); } }); @@ -297,6 +311,8 @@ export class SQLiteStorage implements Storage { peerId: row.peer_id, role: row.role, candidate: row.candidate, + sdpMid: row.sdp_mid, + sdpMLineIndex: row.sdp_m_line_index, createdAt: row.created_at, })); } diff --git a/src/storage/types.ts b/src/storage/types.ts index 9968f9b..185213d 100644 --- a/src/storage/types.ts +++ b/src/storage/types.ts @@ -23,6 +23,8 @@ export interface IceCandidate { peerId: string; role: 'offerer' | 'answerer'; candidate: string; + sdpMid: string | null; + sdpMLineIndex: number | null; createdAt: number; } @@ -125,14 +127,18 @@ export interface Storage { * @param offerId Offer identifier * @param peerId Peer ID posting the candidates * @param role Role of the peer (offerer or answerer) - * @param candidates Array of ICE candidate strings + * @param candidates Array of ICE candidate objects * @returns Number of candidates added */ addIceCandidates( offerId: string, peerId: string, role: 'offerer' | 'answerer', - candidates: string[] + candidates: Array<{ + candidate: string; + sdpMid?: string | null; + sdpMLineIndex?: number | null; + }> ): Promise; /**