import { mkdir, readFile, writeFile, rename } from 'node:fs/promises' import { join } from 'node:path' import { ASSISTANT_INSTRUCTION, VOICE_TOOLS } from '../../mobile/src/behavior.mjs' export class MobileVault { constructor(directory, safeStorage) { this.directory = directory; this.safeStorage = safeStorage } async load() { if (!this.safeStorage.isEncryptionAvailable()) throw new Error('Windows güvenli anahtar kasası kullanılamıyor.') try { const data = await readFile(join(this.directory, 'mobile-secrets.bin')) return JSON.parse(this.safeStorage.decryptString(data)) } catch (error) { if (error.code === 'ENOENT') return {}; throw new Error('Mobil anahtar kasası açılamadı.') } } async save(value) { if (!this.safeStorage.isEncryptionAvailable()) throw new Error('Windows güvenli anahtar kasası kullanılamıyor.') const encrypted = this.safeStorage.encryptString(JSON.stringify(value)) await mkdir(this.directory, { recursive: true }) const file = join(this.directory, 'mobile-secrets.bin') await writeFile(file + '.tmp', encrypted, { mode: 0o600 }) await rename(file + '.tmp', file) } } export const LIVE_MODEL = 'gemini-3.1-flash-live-preview' export class LiveTokenIssuer { constructor(vault, fetcher = fetch, now = Date.now) { this.vault = vault; this.fetcher = fetcher; this.now = now; this.last = new Map() } async issue(deviceId) { const now = this.now() if (now - (this.last.get(deviceId) ?? -Infinity) < 30000) throw new Error('Yeni ses bağlantısı için 30 saniye bekleyin.') this.last.set(deviceId, now) const secrets = await this.vault.load() if (!secrets.googleKey) throw new Error('Önce kendi Google AI Studio anahtarınızı ekleyin.') let response try { response = await this.fetcher('https://generativelanguage.googleapis.com/v1beta/auth_tokens', { method: 'POST', headers: { 'x-goog-api-key': secrets.googleKey, 'Content-Type': 'application/json' }, body: JSON.stringify({ uses: 1, expireTime: new Date(now + 10 * 60000).toISOString(), newSessionExpireTime: new Date(now + 60000).toISOString(), bidiGenerateContentSetup: { model: `models/${LIVE_MODEL}`, generationConfig: { responseModalities: ['AUDIO'] }, systemInstruction: { parts: [{ text: ASSISTANT_INSTRUCTION }] }, tools: [{ functionDeclarations: VOICE_TOOLS }], inputAudioTranscription: {}, outputAudioTranscription: {} } }), redirect: 'error', signal: AbortSignal.timeout(20000) }) } catch { throw new Error('Google ses erişimine ulaşılamadı.') } if (!response.ok) throw new Error(response.status === 429 ? 'Google kotası veya istek sınırı doldu. AI Studio üzerinden kontrol edin.' : response.status === 401 || response.status === 403 ? 'Google anahtarı veya ses modeli erişimi geçersiz.' : 'Google ses erişimi oluşturulamadı.') const data = await response.json() if (typeof data.name !== 'string' || !data.name.startsWith('auth_tokens/')) throw new Error('Google geçerli kısa ömürlü erişim döndürmedi.') return { token: data.name, model: LIVE_MODEL, expiresAt: new Date(now + 10 * 60000).toISOString() } } }