import { app, safeStorage } from 'electron' import { mkdtemp, readFile, writeFile } from 'node:fs/promises' import { tmpdir } from 'node:os' import { resolve, join } from 'node:path' import assert from 'node:assert/strict' import { createMobileWindow } from '../../desktop/electron/mobile-host.mjs' import { MobileVault } from '../../desktop/electron/mobile-secrets.mjs' const directory = await mkdtemp(join(tmpdir(), 'aiturk-mobile-electron-')) app.setPath('userData', join(directory, 'electron-profile')) void app.whenReady().then(async () => { let window try { assert.ok(safeStorage.isEncryptionAvailable(), 'Windows encryption unavailable') window = createMobileWindow({ directory, assets: resolve('apps/desktop/dist'), show: false, resolveBackend: async () => { throw new Error('No live backend in vault smoke test') } }) await new Promise(resolve => window.webContents.once('did-finish-load', resolve)) const result = await window.webContents.executeJavaScript(`(async () => { const before = await mobileHost.call('setup'); await mobileHost.call('account.save', {token:'local-fake-account-for-encryption-test'}); const after = await mobileHost.call('setup'); return {before,after,heading:document.querySelector('h1').textContent}; })()`) assert.equal(result.before.hasAccount, false); assert.equal(result.after.hasAccount, true) const blob = await readFile(join(directory, 'mobile', 'mobile-secrets.bin')) assert.ok(!blob.includes(Buffer.from('local-fake-account-for-encryption-test'))) const vault = new MobileVault(join(directory, 'mobile'), safeStorage) assert.equal((await vault.load()).accountToken, 'local-fake-account-for-encryption-test') await vault.save({ ...await vault.load(), deviceId: '11111111-1111-4111-8111-111111111111' }) const fingerprint = await window.webContents.executeJavaScript(`(async () => { const { createDeviceIdentity } = await import('./mobile/peer-identity.mjs'); const setup = await mobileHost.call('setup'); const identity = await createDeviceIdentity(setup.deviceId); await mobileHost.call('identity.save', {deviceId:setup.deviceId, fingerprint:identity.fingerprint}); let rejected = false; try { await mobileHost.call('identity.save', {deviceId:setup.deviceId, fingerprint:Array(32).fill('00').join(':')}); } catch { rejected = true; } if (!rejected) throw new Error('Changed identity silently accepted'); return identity.fingerprint; })()`) assert.equal((await vault.load()).localFingerprint, fingerprint) const reloaded = new Promise(resolve => window.webContents.once('did-finish-load', resolve)) window.webContents.reload(); await reloaded const restored = await window.webContents.executeJavaScript(`(async () => { const { loadDeviceIdentity } = await import('./mobile/peer-identity.mjs'); const setup = await mobileHost.call('setup'); const identity = await loadDeviceIdentity(setup.deviceId, setup.localFingerprint); return identity.fingerprint; })()`) assert.equal(restored, fingerprint) const originalFetch = globalThis.fetch, phoneId = '33333333-3333-4333-8333-333333333333' const pairingId = '44444444-4444-4444-8444-444444444444', phonePin = Array(32).fill('AB').join(':') let advertisedPin = phonePin, approvals = 0 globalThis.fetch = async (url, options) => { const path = new URL(url).pathname if (path === `/v1/aiturk/pairings/${pairingId}/approve` && options.method === 'POST') { approvals++ return Response.json({ device_id: phoneId, status: 'approved' }) } if (path === '/v1/aiturk/mobile/channels') return Response.json({ channels: [{ id: 'fixture-channel', phone_id: phoneId, sdp: `v=0\r\na=fingerprint:sha-256 ${advertisedPin}\r\n` }] }) throw new Error('Unexpected network request in isolated test') } try { const proof = JSON.stringify({ kind: 'aiturk-phone-identity', version: 1, pairingId, deviceId: phoneId, fingerprint: phonePin }) const qrImport = await window.webContents.executeJavaScript(`(async () => { const { drawQr } = await import('./mobile/qr.mjs'); const canvas = document.createElement('canvas'); drawQr(canvas, ${JSON.stringify(proof)}); const blob = await new Promise(resolve => canvas.toBlob(resolve, 'image/png')); const transfer = new DataTransfer(); transfer.items.add(new File([blob], 'phone-identity.png', {type:'image/png'})); const input = document.getElementById('phone-identity-file'); input.files = transfer.files; await input.onchange({target:input}); return {pairingId:document.getElementById('pairing').value, proof:document.getElementById('phone-identity').value, status:document.getElementById('status').textContent}; })()`) assert.equal(qrImport.pairingId, pairingId) assert.equal(qrImport.proof, proof) assert.match(qrImport.status, /henüz izin verilmedi/) assert.equal(approvals, 0) const accepted = await window.webContents.executeJavaScript(`mobileHost.call('phone.approve', ${JSON.stringify({ pairingId, code: 'QRSTUVWX', identity: proof })})`) assert.equal(accepted.device_id, phoneId) assert.equal((await vault.load()).phoneFingerprints[phoneId], phonePin) const inbox = await window.webContents.executeJavaScript(`mobileHost.call('inbox')`) assert.equal(inbox.channels[0].trustedPhoneFingerprint, phonePin) advertisedPin = Array(32).fill('CD').join(':') await assert.rejects(window.webContents.executeJavaScript(`mobileHost.call('inbox')`), /kimliği/) await assert.rejects(window.webContents.executeJavaScript(`mobileHost.call('channel.answer', {channelId:'fixture-channel',sdp:'unused'})`), /kimliği/) const changed = JSON.stringify({ kind: 'aiturk-phone-identity', version: 1, pairingId, deviceId: phoneId, fingerprint: advertisedPin }) await assert.rejects(window.webContents.executeJavaScript(`mobileHost.call('phone.approve', ${JSON.stringify({ pairingId, code: 'QRSTUVWX', identity: changed })})`), /kimliği/) assert.equal(approvals, 1) assert.equal((await vault.load()).phoneFingerprints[phoneId], phonePin) } finally { globalThis.fetch = originalFetch } await vault.save({}) const receipt = { realElectronIpc: 'passed', windowsSafeStorage: 'passed', desktopIdentityReload: 'passed', silentIdentityReplacement: 'rejected', locallyApprovedPhonePin: 'passed', changedPhoneBeforeAnswer: 'rejected', identityQrImportWithoutApproval: 'passed', heading: result.heading, checkedAt: new Date().toISOString() } await writeFile('apps/mobile/test-results/electron-smoke.json', JSON.stringify(receipt)) console.log(JSON.stringify(receipt)) window.destroy(); app.exit(0) } catch (error) { console.error(error); window?.destroy(); app.exit(1) } })