feat(mobile): connect Android and IDE to browser account login
This commit is contained in:
1 parent
b66daed07a
commit
c1929957a2
8 files changed
+109
-8
No files matched your search
@@ -2,6 +2,9 @@ import { completeIce, rtcConfiguration, sendFrame } from './mobile/transport.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 } } }
|
||||
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('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ı.') })
|
||||
|
||||
@@ -4,8 +4,12 @@
|
||||
<p class="eyebrow">AITURK · BİLGİSAYAR</p><h1>Telefonunu bağla.</h1>
|
||||
<p>Bu pencere açıkken eşleştirdiğiniz Android telefon seçtiğiniz IDE konuşmasına erişebilir. Google anahtarı Windows güvenli kasasında tutulur.</p>
|
||||
<p id="status" role="status" aria-live="polite">Bağlantı kapalı.</p>
|
||||
<button id="login-start">AITURK hesabımla giriş yap</button><p id="login-code"></p><button id="login-check">Girişi kontrol et</button>
|
||||
<p>Çıkış, mobil bağlantıları kapatır ve bu penceredeki hesap/Google anahtarını Windows kasasından temizler; başlamış IDE işini durdurmaz.</p>
|
||||
<button id="logout">Mobil oturumdan çık ve kasayı temizle</button>
|
||||
<details><summary>Gelişmiş: hesap API anahtarı</summary>
|
||||
<label>AITURK hesap API anahtarı<input id="account" type="password" autocomplete="off"></label>
|
||||
<button id="save">Hesap anahtarını kaydet</button><button id="pair">Bu bilgisayarı hesabımla eşleştir</button>
|
||||
<button id="save">Hesap anahtarını kaydet</button></details><button id="pair">Bu bilgisayarı hesabımla eşleştir</button>
|
||||
<p id="code"></p><hr>
|
||||
<h2>Telefona izin ver</h2><p>Telefondaki eşleştirme kimliğini ve 8 karakterli kodu buraya girin. Bu onay yalnız bu telefon ile bu bilgisayarı bağlar.</p>
|
||||
<label>Eşleştirme kimliği<input id="pairing"></label><label>Telefondaki kod<input id="phone-code" maxlength="8" autocapitalize="characters"></label>
|
||||
|
||||
@@ -33,6 +33,31 @@ export function createMobileWindow({ directory, assets, resolveBackend, show = t
|
||||
await vault.save({ ...previous, accountToken: params.token })
|
||||
return { saved: true }
|
||||
}
|
||||
if (action === 'account.login.start') {
|
||||
const previous = await vault.load()
|
||||
if (previous.accountToken || clients.size) throw new Error('Kayıtlı hesap var. Önce mevcut cihazı iptal edin.')
|
||||
const login = await new AccountClient({}).call('/v1/aiturk/mobile-auth/start', {})
|
||||
if (!/^https:\/\/turkservis\.online\/account\/aiturk\/mobile-login\/[0-9a-f-]{36}$/.test(login.verificationUri)) throw new Error('Geçersiz giriş adresi.')
|
||||
await vault.save({ ...previous, pendingLogin: login })
|
||||
await shell.openExternal(login.verificationUri)
|
||||
return { code: login.userCode }
|
||||
}
|
||||
if (action === 'account.login.check') {
|
||||
const previous = await vault.load()
|
||||
if (!previous.pendingLogin) throw new Error('Önce giriş başlatın.')
|
||||
const result = await new AccountClient({ accountToken: previous.pendingLogin.deviceCode }).call('/v1/aiturk/mobile-auth/poll', {})
|
||||
if (result.state !== 'approved') return { approved: false }
|
||||
delete previous.pendingLogin
|
||||
await vault.save({ ...previous, accountToken: result.accountToken })
|
||||
return { approved: true }
|
||||
}
|
||||
if (action === 'account.logout') {
|
||||
const previous = await vault.load(), token = previous.accountToken || previous.pendingLogin?.deviceCode
|
||||
if (token?.startsWith('tsml_')) await new AccountClient({ accountToken: token }).call('/v1/aiturk/mobile-auth/logout', {})
|
||||
for (const [id, entry] of clients) { entry.client.close(); send({ channelId: id, close: true }) }
|
||||
clients.clear(); await vault.save({})
|
||||
return { cleared: 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,
|
||||
|
||||
@@ -8,7 +8,9 @@ turkservis.online veya kurulu IDE bu kaynak değişiklikleriyle güncellenmedi.
|
||||
## Çalışan ve test edilen parçalar
|
||||
|
||||
- Capacitor 8.5.1 Android projesi; Türkçe erişilebilir kurulum ve yazılı kullanım.
|
||||
- Mevcut AITURK hesap API anahtarı ve cihaz sırrıyla hesap/cihaz doğrulaması.
|
||||
- Tarayıcıda mevcut AITURK hesabıyla kısa kod onayı; uygulamaya parola girilmez.
|
||||
Dar kapsamlı mobil oturum Windows/Android kasasında tutulur. Mevcut hesap API
|
||||
anahtarı gelişmiş seçenek olarak korunur. Bu yeni giriş henüz canlıya dağıtılmadı.
|
||||
Telefonun eşleşmesini hedef bilgisayar kendi cihaz tokenıyla onaylar.
|
||||
- WebRTC DTLS/SCTP veri kanalı; büyük Unicode mesajları sınırlı parçalara bölme.
|
||||
- Mevcut IDE `/api/sessions` ve JSON-RPC adaptörü. Konuşma ve seçili model
|
||||
@@ -50,9 +52,10 @@ Derlenmiş IDE'yi `--aiturk-mobile` parametresiyle açın. IDE zaten açıksa pa
|
||||
mobil bağlantı penceresini açar; çalışmakta olan konuşma kapatılmaz.
|
||||
Mobil pencere açılmadan uzaktan erişim etkinleşmez.
|
||||
|
||||
1. Bilgisayardaki mobil pencerede AITURK hesap API anahtarını kaydedin.
|
||||
1. Bilgisayardaki mobil pencerede “AITURK hesabımla giriş yap” seçin; tarayıcıda
|
||||
hesabınıza girip uygulamanın kodunu onaylayın. “Girişi kontrol et” ile tamamlayın.
|
||||
2. Bilgisayarı hesapla eşleştirin; açılan mevcut portal sayfasında kodu onaylayın.
|
||||
3. Android'de aynı hesap anahtarıyla eşleştirme kodu oluşturun.
|
||||
3. Android'de aynı AITURK hesabıyla tarayıcı kod girişini tamamlayıp eşleştirme kodu oluşturun.
|
||||
4. Telefondaki kimlik/kodu bilgisayardaki mobil pencerede açıkça onaylayın.
|
||||
5. Bilgisayarda “Mobil bağlantıyı aç”, telefonda “Bilgisayarları bul” ve “Bağlan”.
|
||||
6. Gerçek IDE konuşmasını seçin. Yazılı görevi görünür önizlemeden gönderin.
|
||||
@@ -89,7 +92,8 @@ Henüz tamamlanmayanlar:
|
||||
- Kullanıcıya ait test anahtarıyla Google Live doğrulaması ve Türkçe konuşma
|
||||
senaryoları. Sıfır gerçek Google çağrısı yapıldı; ücret oluştuğu iddia edilmiyor.
|
||||
- Canlı gateway/TURN kurulumu, ayrı mobil ve bilgisayar ağlarıyla tam akış.
|
||||
- QR tarama ve tek dokunuşla hesap girişi; ilk sürüm kod/hesap anahtarı kullanır.
|
||||
- QR tarama. Tarayıcı kısa kod girişi kodlandı ve HTTP/DB ile sahte yanıtlı
|
||||
tarayıcı arayüz testleri geçti; gerçek Android giriş kabulü henüz yapılmadı.
|
||||
- Açık talimatla prepare_task/submit_task devri ve görev kimlikli terminal
|
||||
olayları kodlandı. 25 Node testi ve üç görevli gerçek yerel IDE bağlantı
|
||||
testi geçti. Canlı Google transkript sırası ve Türkçe kalite kabulü henüz yok.
|
||||
|
||||
+10
@@ -26,6 +26,16 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
public class MobileSecretsPlugin extends Plugin {
|
||||
private static final String ALIAS = "aiturk.mobile.account.v1";
|
||||
private final AtomicInteger requests = new AtomicInteger();
|
||||
@PluginMethod public void openLogin(PluginCall call) {
|
||||
String url = call.getString("url", "");
|
||||
if (!url.matches("https://turkservis\\.online/account/aiturk/mobile-login/[0-9a-f-]{36}")) {
|
||||
call.reject("Geçersiz giriş adresi."); return;
|
||||
}
|
||||
try {
|
||||
getActivity().startActivity(new android.content.Intent(android.content.Intent.ACTION_VIEW, android.net.Uri.parse(url)));
|
||||
call.resolve();
|
||||
} catch (Exception error) { call.reject("Tarayıcı açılamadı."); }
|
||||
}
|
||||
@PluginMethod public void request(PluginCall call) {
|
||||
String path = call.getString("path", "");
|
||||
String method = call.getString("method", "GET");
|
||||
|
||||
@@ -23,6 +23,23 @@ try {
|
||||
await page.getByRole('heading', { name: 'Fikrini konuş. Birlikte geliştir.' }).waitFor()
|
||||
assert.equal(await page.evaluate(() => document.documentElement.scrollWidth > innerWidth), false)
|
||||
assert.ok((await page.getByRole('button', { name: 'Eşleştirme kodu oluştur' }).boundingBox()).height >= 48)
|
||||
// UI wiring only: real account authentication is covered by gateway HTTP/DB
|
||||
// tests. Never submit a credential to the live portal from this smoke test.
|
||||
const loginCalls = []
|
||||
await page.route('https://turkservis.online/v1/aiturk/mobile-auth/**', async route => {
|
||||
const action = new URL(route.request().url()).pathname.split('/').at(-1); loginCalls.push(action)
|
||||
const result = action === 'start' ? { deviceCode: 'tsml_fixture', userCode: 'ABCDEFGH',
|
||||
verificationUri: 'https://turkservis.online/account/aiturk/mobile-login/11111111-1111-4111-8111-111111111111' }
|
||||
: action === 'poll' ? { state: 'approved', accountToken: 'tsml_fixture' } : { revoked: true }
|
||||
await route.fulfill({ status: 200, contentType: 'application/json', headers: { 'Access-Control-Allow-Origin': '*' }, body: JSON.stringify(result) })
|
||||
})
|
||||
await page.getByRole('button', { name: 'AITURK hesabımla giriş yap' }).click()
|
||||
await page.getByText('Yalnız kendi hesabınızda bu kodu onaylayın: ABCDEFGH').waitFor()
|
||||
await page.getByRole('button', { name: 'Girişi kontrol et' }).click()
|
||||
await page.getByText('Hesaba giriş yapıldı.', { exact: true }).waitFor()
|
||||
await page.getByRole('button', { name: 'Bu uygulamadaki hesaptan çık' }).click()
|
||||
await page.getByText('Bu uygulamanın oturumu kapatıldı.', { exact: false }).waitFor()
|
||||
assert.deepEqual(loginCalls, ['start', 'poll', 'logout'])
|
||||
await mkdir('test-results', { recursive: true })
|
||||
await page.screenshot({ path: 'test-results/android-onboarding.png', fullPage: true })
|
||||
const result = await page.evaluate(async () => {
|
||||
@@ -50,5 +67,5 @@ try {
|
||||
})
|
||||
assert.equal(result.length, result.expected); assert.equal(result.dtls, 'connected'); assert.equal(result.action, 'session.history')
|
||||
assert.deepEqual(errors, [])
|
||||
console.log(JSON.stringify({ onboarding: 'passed', realWebRTC: result, errors }))
|
||||
console.log(JSON.stringify({ onboarding: 'passed', mockedLoginUi: 'passed', realWebRTC: result, errors }))
|
||||
} finally { await browser.close(); server.close() }
|
||||
+33
-1
@@ -30,8 +30,40 @@ function event(frame) {
|
||||
}
|
||||
}
|
||||
}
|
||||
action('login-start', async () => {
|
||||
if (credentials.accountToken || connection) throw new Error('Kayıtlı hesabınız var; önce mevcut cihaz erişimini iptal edin.')
|
||||
const result = await new AccountClient({}).call('/v1/aiturk/mobile-auth/start', {})
|
||||
if (!/^https:\/\/turkservis\.online\/account\/aiturk\/mobile-login\/[0-9a-f-]{36}$/.test(result.verificationUri)) throw new Error('Geçersiz giriş sayfası.')
|
||||
credentials.pendingLogin = result; await saveCredentials()
|
||||
$('login-code').textContent = `Yalnız kendi hesabınızda bu kodu onaylayın: ${result.userCode}`
|
||||
$('login-link').href = result.verificationUri; $('login-link').hidden = false
|
||||
if (native()) await native().openLogin({ url: result.verificationUri })
|
||||
status('Tarayıcıda giriş yaptıktan sonra buraya dönüp girişi kontrol edin.')
|
||||
})
|
||||
action('login-check', async () => {
|
||||
if (!credentials.pendingLogin) throw new Error('Önce giriş başlatın.')
|
||||
const result = await new AccountClient({ accountToken: credentials.pendingLogin.deviceCode }).call('/v1/aiturk/mobile-auth/poll', {})
|
||||
if (result.state !== 'approved') { status('Tarayıcıdaki kod onayı bekleniyor.'); return }
|
||||
credentials.accountToken = result.accountToken; delete credentials.pendingLogin; await saveCredentials()
|
||||
account = new AccountClient(credentials); $('login-code').textContent = 'Hesaba giriş yapıldı.'; $('login-link').hidden = true
|
||||
status('Hesap bağlı. Şimdi telefonun eşleştirme kodunu oluşturun.')
|
||||
})
|
||||
action('logout', async () => {
|
||||
const token = credentials.accountToken || credentials.pendingLogin?.deviceCode
|
||||
// Revoke the app-specific credential before dropping the local copy. A
|
||||
// network failure leaves it available for an explicit retry.
|
||||
if (token?.startsWith('tsml_')) await new AccountClient({ accountToken: token }).call('/v1/aiturk/mobile-auth/logout', {})
|
||||
connectionEpoch++; stopVoice(); connection?.close(); connection = null; selected = null; account = null
|
||||
credentials = {}; pendingTask = null; await saveCredentials()
|
||||
$('work').hidden = true; $('login-link').hidden = true; $('login-code').textContent = ''; $('pair-code').textContent = ''
|
||||
status('Bu uygulamanın oturumu kapatıldı. IDE’de başlamış iş kendi çalışma kurallarıyla devam edebilir.')
|
||||
})
|
||||
action('pair', async () => {
|
||||
credentials = { accountToken: $('account-token').value.trim() }; $('account-token').value = ''
|
||||
if (credentials.deviceId) throw new Error('Bu telefon zaten eşleşme başlatmış. Bilgisayarları bulun veya mevcut erişimi iptal edin.')
|
||||
const manual = $('account-token').value.trim(); $('account-token').value = ''
|
||||
if (manual && credentials.accountToken && manual !== credentials.accountToken) throw new Error('Kayıtlı hesap sessizce değiştirilemez.')
|
||||
if (manual) credentials.accountToken = manual
|
||||
if (!credentials.accountToken) throw new Error('Önce AITURK hesabınıza giriş yapın.')
|
||||
account = new AccountClient(credentials)
|
||||
const result = await account.call('/v1/aiturk/pairings', { display_name: $('device-name').value.trim(), platform: 'android' })
|
||||
Object.assign(credentials, { deviceId: result.device_id, deviceToken: result.device_token, pairingId: result.pairing_id })
|
||||
|
||||
@@ -4,8 +4,14 @@
|
||||
<p class="eyebrow">AITURK · ANDROID GELİŞTİRME SÜRÜMÜ</p><h1>Fikrini konuş.<br>Birlikte geliştir.</h1>
|
||||
<p id="status" class="status" role="status" aria-live="polite">Bilgisayarına bağlanarak başla.</p>
|
||||
<section id="setup"><h2>1. Aynı AITURK hesabı</h2>
|
||||
<p>Bu ilk geliştirme sürümünde hesabınızın API anahtarıyla giriş yapılır. <a href="https://turkservis.online/account" target="_blank" rel="noreferrer">AITURK hesabınızı açın</a>. Bu anahtar Google ses anahtarından ayrıdır.</p>
|
||||
<p>Tarayıcıda AITURK hesabınıza giriş yapın ve burada gösterilen kısa kodu onaylayın. Google ses anahtarı ayrı kalır.</p>
|
||||
<button id="login-start">AITURK hesabımla giriş yap</button><pre id="login-code" aria-live="polite"></pre>
|
||||
<a id="login-link" hidden target="_blank" rel="noreferrer">Giriş sayfasını aç</a>
|
||||
<button id="login-check" class="secondary">Girişi kontrol et</button>
|
||||
<button id="logout" class="secondary">Bu uygulamadaki hesaptan çık</button>
|
||||
<details><summary>Gelişmiş: mevcut hesap API anahtarını kullan</summary>
|
||||
<label>AITURK hesap API anahtarı<input id="account-token" type="password" autocomplete="off" autocapitalize="none" spellcheck="false"></label>
|
||||
</details>
|
||||
<label>Telefon adı<input id="device-name" maxlength="80" value="Android telefonum"></label><button id="pair">Eşleştirme kodu oluştur</button>
|
||||
<pre id="pair-code" aria-live="polite"></pre><p>Kodu bilgisayardaki AITURK Mobil bağlantı penceresinde onaylayın. Eşleştirme kimliğini kopyalayabilirsiniz.</p>
|
||||
<button id="devices" class="secondary">Onaydan sonra bilgisayarları bul</button>
|
||||
|
||||
Reference in new issue
Block a user