53 lines
3.2 KiB
JavaScript
53 lines
3.2 KiB
JavaScript
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() }
|
||
}
|
||
}
|