feat(mobile): bind device identities and bound voice usage in 0.1.4 dev
This commit is contained in:
1 parent
ac6687181e
commit
5f75dcc99a
24 files changed
+909
-47
No files matched your search
@@ -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 }
|
||||
|
||||
Reference in new issue
Block a user