104 lines
6.2 KiB
JavaScript
104 lines
6.2 KiB
JavaScript
import { join } from 'node:path'
|
||
import { hostname } from 'node:os'
|
||
import { BrowserWindow, ipcMain, safeStorage, shell } from 'electron'
|
||
import { MobileVault, LiveTokenIssuer } from './mobile-secrets.mjs'
|
||
import { DeliveryJournal, MobileGatewayClient } from './mobile-adapter.mjs'
|
||
import { AccountClient } from '../../mobile/src/transport.mjs'
|
||
|
||
export function createMobileWindow({ directory, assets, resolveBackend, show = true }) {
|
||
const vault = new MobileVault(join(directory, 'mobile'), safeStorage)
|
||
const journal = new DeliveryJournal(join(directory, 'mobile', 'deliveries'))
|
||
const issuer = new LiveTokenIssuer(vault)
|
||
const clients = new Map()
|
||
const window = new BrowserWindow({ width: 560, height: 800, show, title: 'AITURK Mobil bağlantısı',
|
||
webPreferences: { preload: join(assets, 'mobile-preload.cjs'), contextIsolation: true,
|
||
nodeIntegration: false, sandbox: true, backgroundThrottling: false } })
|
||
window.setMenu(null)
|
||
window.webContents.setWindowOpenHandler(() => ({ action: 'deny' }))
|
||
window.webContents.on('will-navigate', event => event.preventDefault())
|
||
const api = async () => new AccountClient(await vault.load())
|
||
const call = async (path, body, method) => (await api()).call(path, body, method)
|
||
const send = frame => { if (!window.isDestroyed()) window.webContents.send('aiturk-mobile-event', frame) }
|
||
const handler = async (event, action, params = {}) => {
|
||
if (event.sender !== window.webContents || event.senderFrame !== window.webContents.mainFrame) throw new Error('Geçersiz mobil pencere.')
|
||
if (action === 'setup') {
|
||
const secrets = await vault.load()
|
||
return { paired: Boolean(secrets.deviceId), hasAccount: Boolean(secrets.accountToken), hasGoogle: Boolean(secrets.googleKey) }
|
||
}
|
||
if (action === 'account.save') {
|
||
if (typeof params.token !== 'string' || params.token.length < 12 || params.token.length > 512) throw new Error('AITURK hesap anahtarı geçersiz.')
|
||
if (clients.size) throw new Error('Hesap değiştirmeden önce mobil bağlantıyı kapatın.')
|
||
const previous = await vault.load()
|
||
if (previous.accountToken && previous.accountToken !== params.token) throw new Error('Hesabı değiştirmek için önce mevcut cihazı iptal edin ve bağlantı penceresini yeniden açın.')
|
||
await vault.save({ ...previous, accountToken: params.token })
|
||
return { saved: true }
|
||
}
|
||
if (action === 'desktop.pair') {
|
||
const result = await call('/v1/aiturk/pairings', { display_name: hostname(), platform: 'windows-x64' })
|
||
await vault.save({ ...await vault.load(), deviceId: result.device_id, deviceToken: result.device_token,
|
||
pairingId: result.pairing_id })
|
||
await shell.openExternal('https://turkservis.online' + result.verification_uri)
|
||
return { code: result.user_code, expiresAt: result.expires_at }
|
||
}
|
||
if (action === 'phone.approve') {
|
||
if (!/^[0-9a-f-]{36}$/.test(params.pairingId) || !/^[A-Z2-9]{8}$/.test(params.code)) throw new Error('Telefondaki eşleştirme bilgileri geçersiz.')
|
||
return call(`/v1/aiturk/pairings/${params.pairingId}/approve`, { user_code: params.code })
|
||
}
|
||
if (action === 'devices') return call('/v1/aiturk/mobile/devices')
|
||
if (action === 'device.revoke') {
|
||
if (!/^[0-9a-f-]{36}$/.test(params.deviceId) || !Number.isInteger(params.version)) throw new Error('Geçersiz cihaz.')
|
||
const result = await call(`/v1/aiturk/devices/${params.deviceId}/revoke`, { expected_version: params.version })
|
||
for (const [id, entry] of clients) if (entry.phoneId === params.deviceId || result.self_revoked) {
|
||
entry.client.close(); clients.delete(id); send({ channelId: id, close: true })
|
||
}
|
||
if (result.self_revoked) await vault.save({})
|
||
return result
|
||
}
|
||
if (action === 'inbox') return call('/v1/aiturk/mobile/channels')
|
||
if (action === 'ice') return call('/v1/aiturk/mobile/ice')
|
||
if (action === 'channel.answer') {
|
||
const inbox = await call('/v1/aiturk/mobile/channels')
|
||
const offer = inbox.channels.find(row => row.id === params.channelId)
|
||
if (!offer || clients.has(offer.id) || clients.size >= 4) throw new Error('Bağlantı isteği artık geçerli değil.')
|
||
const authorize = () => call(`/v1/aiturk/mobile/channels/${offer.id}/heartbeat`, {})
|
||
const client = new MobileGatewayClient(resolveBackend, frame => send({ channelId: offer.id, frame }),
|
||
{ deviceId: offer.phone_id, journal, authorize })
|
||
await call(`/v1/aiturk/mobile/channels/${offer.id}/answer`, { sdp: params.sdp })
|
||
clients.set(offer.id, { client, phoneId: offer.phone_id, authorize })
|
||
return { accepted: true }
|
||
}
|
||
if (action === 'channel.heartbeat') {
|
||
const entry = clients.get(params.channelId)
|
||
if (!entry) throw new Error('Bağlantı yok.')
|
||
return entry.authorize()
|
||
}
|
||
if (action === 'channel.close') {
|
||
clients.get(params.channelId)?.client.close(); clients.delete(params.channelId)
|
||
return { closed: true }
|
||
}
|
||
if (action === 'channel.request') {
|
||
const entry = clients.get(params.channelId)
|
||
if (!entry) throw new Error('Bağlantı yok.')
|
||
await entry.authorize()
|
||
const message = params.message
|
||
if (!message || JSON.stringify(message).length > 65000) throw new Error('İstek boyutu geçersiz.')
|
||
if (message.action === 'google.key.save') {
|
||
const key = message.params?.key
|
||
if (typeof key !== 'string' || !/^[A-Za-z0-9_-]{20,256}$/.test(key)) throw new Error('Google anahtarı biçimi geçersiz.')
|
||
await vault.save({ ...await vault.load(), googleKey: key })
|
||
return { saved: true }
|
||
}
|
||
if (message.action === 'google.key.delete') {
|
||
const secrets = await vault.load(); delete secrets.googleKey; await vault.save(secrets); return { deleted: true }
|
||
}
|
||
if (message.action === 'voice.token') return issuer.issue(entry.phoneId)
|
||
return entry.client.handle(message)
|
||
}
|
||
throw new Error('Desteklenmeyen mobil işlem.')
|
||
}
|
||
ipcMain.handle('aiturk-mobile', handler)
|
||
window.on('closed', () => { for (const entry of clients.values()) entry.client.close(); clients.clear(); ipcMain.removeHandler('aiturk-mobile') })
|
||
void window.loadFile(join(assets, 'mobile-host.html'))
|
||
return window
|
||
}
|