diff --git a/apps/desktop/electron/mobile-host-ui.mjs b/apps/desktop/electron/mobile-host-ui.mjs index 23b1ff3..84b2b87 100644 --- a/apps/desktop/electron/mobile-host-ui.mjs +++ b/apps/desktop/electron/mobile-host-ui.mjs @@ -1,16 +1,45 @@ import { completeIce, rtcConfiguration, sendFrame } from './mobile/transport.mjs' -import { drawQr } from './mobile/qr.mjs' +import { drawQr, InviteScanner } from './mobile/qr.mjs' +import { createDeviceIdentity, loadDeviceIdentity, assertRemoteIdentity, parsePhoneIdentity } from './mobile/peer-identity.mjs' const host = window.mobileHost, $ = id => document.getElementById(id), peers = new Map() const status = text => { $('status').textContent = text } function action(id, work) { $(id).onclick = async () => { $(id).disabled = true; try { await work() } catch (error) { status(error.message) } finally { $(id).disabled = false } } } +const identityScanner = new InviteScanner(document.createElement('video'), async raw => { + if (raw.length > 700) throw new Error('Telefon kimlik belgesi çok büyük.') + const value = JSON.parse(raw) + const pairingId = $('pairing').value.trim() || value.pairingId + parsePhoneIdentity(raw, pairingId) + if (!/^[0-9a-f-]{36}$/.test(pairingId)) throw new Error('Eşleştirme kimliği geçersiz.') + $('pairing').value = pairingId; $('phone-identity').value = raw + status('Telefon kimliği QR resminden alındı. Telefondaki kısa kodu girin ve bu telefonu ayrıca onaylayın; henüz izin verilmedi.') +}) +$('phone-identity-file').onchange = async event => { + try { await identityScanner.image(event.target.files[0]) } + catch (error) { status(error.message) } + finally { event.target.value = '' } +} action('login-start', async () => { const result = await host.call('account.login.start'); $('login-code').textContent = `Açılan hesap sayfasında yalnız bu kodu onaylayın: ${result.code}` }) action('login-check', async () => { const result = await host.call('account.login.check'); status(result.approved ? 'Hesaba giriş yapıldı. Bu bilgisayarı eşleştirebilirsiniz.' : 'Tarayıcıdaki kod onayı bekleniyor.') }) action('logout', async () => { await host.call('account.logout'); listening = false; $('login-code').textContent = ''; status('Mobil oturum kapatıldı; hesap ve Google anahtarı bu bilgisayar kasasından temizlendi.') }) -action('invite', async () => { const invite = await host.call('invite.create'); drawQr($('invite-qr'), JSON.stringify(invite)); $('invite-qr').hidden = false; $('invite-status').textContent = `Telefonda aynı hesaba giriş yapıp bu QR kodunu tarayın. Son geçerlilik: ${invite.expiresAt}` }) +async function desktopIdentity(create = false) { + const setup = await host.call('setup') + if (!setup.deviceId) throw new Error('Önce bu bilgisayarı hesapla eşleştirin.') + if (setup.localFingerprint) return loadDeviceIdentity(setup.deviceId, setup.localFingerprint) + if (!create) throw new Error('Önce bu bilgisayarda yeni telefon QR kodu oluşturun.') + const identity = await createDeviceIdentity(setup.deviceId) + await host.call('identity.save', { deviceId: setup.deviceId, fingerprint: identity.fingerprint }) + return identity +} +action('invite', async () => { + const identity = await desktopIdentity(true), invite = await host.call('invite.create') + drawQr($('invite-qr'), JSON.stringify({ ...invite, version: 2, desktopFingerprint: identity.fingerprint })) + $('invite-qr').hidden = false + $('invite-status').textContent = `Telefonda aynı hesaba giriş yapıp bu QR kodunu tarayın. Son geçerlilik: ${invite.expiresAt}` +}) action('invite-check', async () => { const result = await host.call('invite.check'); if (!result.pending) { status('QR ile gelen telefon henüz yok.'); return }; $('pairing').value = result.pending.pairingId; status(`${result.pending.name} bulundu. Telefondaki kısa kodu girip “Bu telefonu onayla” seçin.`) }) action('save', async () => { const token = $('account').value; $('account').value = ''; await host.call('account.save', { token }); status('Hesap anahtarı Windows kasasına kaydedildi.') }) action('pair', async () => { const pair = await host.call('desktop.pair'); $('code').textContent = `Açılan hesap sayfasında bu kodu onaylayın: ${pair.code}. Süre: ${pair.expiresAt}` }) -action('approve', async () => { await host.call('phone.approve', { pairingId: $('pairing').value.trim(), code: $('phone-code').value.trim().toUpperCase() }); $('phone-code').value = ''; status('Telefon bu bilgisayarda onaylandı.') }) +action('approve', async () => { await host.call('phone.approve', { pairingId: $('pairing').value.trim(), code: $('phone-code').value.trim().toUpperCase(), identity: $('phone-identity').value.trim() }); $('phone-code').value = ''; $('phone-identity').value = ''; status('Telefon bu bilgisayarda kimliğiyle onaylandı.') }) action('refresh', async () => { const data = await host.call('devices'); $('devices').replaceChildren() for (const device of data.devices) { @@ -24,8 +53,10 @@ action('refresh', async () => { function close(id) { const entry = peers.get(id); if (!entry) return; peers.delete(id); clearInterval(entry.timer); entry.pc.close(); void host.call('channel.close', { channelId: id }) } host.onEvent(event => { if (event.close) { close(event.channelId); return }; const entry = peers.get(event.channelId); if (entry?.channel?.readyState === 'open') sendFrame(entry.channel, event.frame) }) async function connect(offer) { + assertRemoteIdentity(offer.sdp, offer.trustedPhoneFingerprint) const ice = await host.call('ice') - const pc = new RTCPeerConnection(rtcConfiguration(ice.iceServers)), entry = { pc }; peers.set(offer.id, entry) + const identity = await desktopIdentity() + const pc = new RTCPeerConnection({ ...rtcConfiguration(ice.iceServers), certificates: [identity.certificate] }), entry = { pc }; peers.set(offer.id, entry) pc.ondatachannel = event => { const channel = event.channel; entry.channel = channel if (channel.label !== 'aiturk-mobile-v1') { close(offer.id); return } diff --git a/apps/desktop/electron/mobile-host.html b/apps/desktop/electron/mobile-host.html index 0918aea..b2ba6ea 100644 --- a/apps/desktop/electron/mobile-host.html +++ b/apps/desktop/electron/mobile-host.html @@ -15,7 +15,10 @@
Telefondaki eşleştirme kimliğini ve 8 karakterli kodu buraya girin. Bu onay yalnız bu telefon ile bu bilgisayarı bağlar.
+ + +Yalnız kendi telefonunuzun gösterdiği belgenin tamamını buraya aktarın. Onay, bu telefonun sertifikasını bilgisayarınıza kaydeder; farklı sertifika yeniden açık eşleştirme gerektirir.
Farklı ağlarda bağlantı kurulması ağınıza ve TURN hizmeti yapılandırmasına bağlıdır. Bu geliştirme sürümünde genel ağ erişimi henüz doğrulanmadı.
-