feat(mobile): bind device identities and bound voice usage in 0.1.4 dev

This commit is contained in:
2026-09-12 12:11:18 +03:00
parent ac6687181e
commit 5f75dcc99a
24 changed files with 909 additions and 47 deletions
+19 -3
View File
@@ -1,3 +1,4 @@
import { normalizeFingerprint, assertRemoteIdentity, loadDeviceIdentity } from './peer-identity.mjs'
export const GATEWAY = 'https://turkservis.online'
async function accountFetch(url, options) {
const plugin = globalThis.Capacitor?.Plugins?.MobileSecrets
@@ -103,10 +104,21 @@ export function rtcConfiguration(iceServers = []) {
}
export async function openPhoneConnection(account, desktopId, onEvent, iceServers = []) {
// Never obtain this trust anchor from the signaling response. It came from
// the QR physically displayed by the desktop and is stored in the phone vault.
const trusted = account.credentials?.desktopFingerprints
if (!trusted || !Object.hasOwn(trusted, desktopId))
throw new Error('Bu bilgisayarın güvenilen cihaz kimliği yok. Bilgisayarda yeni QR oluşturup yeniden eşleştirin.')
const fingerprint = normalizeFingerprint(trusted[desktopId])
const identity = await loadDeviceIdentity(account.credentials.deviceId, account.credentials.localFingerprint)
if (!iceServers.length) iceServers = (await account.call('/v1/aiturk/mobile/ice')).iceServers
const pc = new RTCPeerConnection(rtcConfiguration(iceServers))
const pc = new RTCPeerConnection({ ...rtcConfiguration(iceServers), certificates: [identity.certificate] })
const channel = pc.createDataChannel('aiturk-mobile-v1', { ordered: true })
const requests = new DataRequests(channel, onEvent)
let established = false
const requests = new DataRequests(channel, frame => {
// Failed handshakes surface their actual error, not a later generic close.
if (established || frame.event !== 'disconnected') onEvent(frame)
})
const id = crypto.randomUUID()
let heartbeat, closed = false
const close = () => { closed = true; clearInterval(heartbeat); channel.close(); pc.close() }
@@ -115,7 +127,10 @@ export async function openPhoneConnection(account, desktopId, onEvent, iceServer
await account.call('/v1/aiturk/mobile/channels', { channel_id: id, desktop_id: desktopId, sdp: pc.localDescription.sdp })
for (let attempt = 0; attempt < 35 && !closed; attempt++) {
const answer = await account.call(`/v1/aiturk/mobile/channels/${id}`)
if (answer.sdp) { await pc.setRemoteDescription({ type: 'answer', sdp: answer.sdp }); break }
if (answer.sdp) {
assertRemoteIdentity(answer.sdp, fingerprint)
await pc.setRemoteDescription({ type: 'answer', sdp: answer.sdp }); break
}
await new Promise(resolve => setTimeout(resolve, 1500))
}
await new Promise((resolve, reject) => {
@@ -125,6 +140,7 @@ export async function openPhoneConnection(account, desktopId, onEvent, iceServer
channel.addEventListener('close', () => { clearTimeout(timer); reject(new Error('Bağlantı kapandı.')) }, { once: true })
})
heartbeat = setInterval(() => { void account.call(`/v1/aiturk/mobile/channels/${id}/heartbeat`, {}).catch(close) }, 15000)
established = true
return { requests, close, pc, id }
} catch (error) { close(); throw error }
}