feat: add brew app connection

- initialize tcp communication with brew app
- WIP value editor sync

Signed-off-by: pakintada@gmail.com <Pakin>
This commit is contained in:
pakintada@gmail.com 2026-04-03 17:25:27 +07:00
parent 08f7626dcb
commit 274025ed33
14 changed files with 431 additions and 69 deletions

View file

@ -1,14 +1,21 @@
import { Adb, AdbDaemonTransport, encodeUtf8 } from '@yume-chan/adb';
import AdbWebCredentialStore from '@yume-chan/adb-credential-web';
import {
AdbDaemonWebUsbDevice,
AdbDaemonWebUsbDeviceManager,
AdbDaemonWebUsbDeviceObserver,
type AdbDaemonWebUsbDevice
AdbDaemonWebUsbDeviceObserver
} from '@yume-chan/adb-daemon-webusb';
import { AdbInstance } from '../../../routes/state.svelte';
import { deviceCredentialManager } from './deviceCredManager';
import { Consumable, MaybeConsumable, ReadableStream } from '@yume-chan/stream-extra';
import { AdbScrcpyClient } from '@yume-chan/adb-scrcpy';
import { addNotification } from '../stores/noti';
import { handleAdbPayload } from '../handlers/adbPayloadHandler';
import { adbWriter } from '../stores/adbWriter';
import { WritableStream } from '@yume-chan/stream-extra';
import { env } from '$env/dynamic/public';
let syncConnection: any = null;
export async function connnectViaWebUSB() {
const device = await AdbDaemonWebUsbDeviceManager.BROWSER?.requestDevice();
@ -27,13 +34,21 @@ export async function connnectViaWebUSB() {
});
const adb = new Adb(transport);
saveAdbInstance(adb);
await saveAdbInstance(adb);
await connectToAndroidServer();
// save device info
await deviceCredentialManager.saveDeviceInfo(device);
} catch (e: any) {
console.error('error on connect', e);
throw new Error(e.toString());
if (e instanceof AdbDaemonWebUsbDevice.DeviceBusyError) {
addNotification(
'ERR:Device is already in use by another program, please close the program and try again'
);
}
throw e;
}
}
}
@ -51,7 +66,9 @@ export async function connectDeviceByCred(
});
const adb = new Adb(transport);
saveAdbInstance(adb);
await saveAdbInstance(adb);
await connectToAndroidServer();
return true;
} catch (error) {
@ -59,7 +76,8 @@ export async function connectDeviceByCred(
}
}
export function saveAdbInstance(adb: Adb | undefined) {
export async function saveAdbInstance(adb: Adb | undefined) {
await cleanupSync();
AdbInstance.instance = adb;
}
@ -100,11 +118,12 @@ export async function executeCmd(command: string) {
};
}
} catch (e: any) {
console.log(e.message);
// console.log(e.message);
//ExactReadable ended
if (e.message.includes('ExactReadable ended')) {
console.error('connection cut off');
return {
output: '',
exitCode: 1,
error: 'ExactReadableEndedError'
};
}
@ -119,32 +138,49 @@ export async function disconnect() {
if (instance) {
await instance.close();
console.log('close instance');
saveAdbInstance(undefined);
await saveAdbInstance(undefined);
}
}
export async function pull(filename: string) {
let instance = getAdbInstance();
if (instance) {
let chunkList: Uint8Array<ArrayBufferLike>[] = [];
let sync = await instance.sync();
const content = sync.read(filename);
let result = content.values();
let res;
let result_string = '';
while ((res = await result.next()) != null) {
// console.log(res.value);
if (res.value != undefined) {
result_string += new TextDecoder().decode(res.value);
}
if (res.done) {
break;
}
export async function cleanupSync() {
if (syncConnection) {
try {
await syncConnection.dispose();
} catch (e) {
console.error('error on dispose sync', e);
}
}
return result_string;
syncConnection = null;
}
export async function pull(filename: string, timeoutMs: number = 5000) {
let instance = getAdbInstance();
await cleanupSync();
try {
if (instance) {
let chunkList: Uint8Array<ArrayBufferLike>[] = [];
const syncProm = instance.sync();
const timeoutProm = new Promise<never>((_, reject) => {
setTimeout(() => reject(new Error('sync timeout')), timeoutMs);
});
syncConnection = await Promise.race([syncProm, timeoutProm]);
const content = syncConnection.read(filename);
let result_string = '';
for await (const chunk of content) {
result_string += new TextDecoder().decode(chunk);
}
return result_string;
}
} catch (pull_error: any) {
console.log('pulling error', pull_error);
} finally {
await cleanupSync();
}
}
@ -176,6 +212,38 @@ export async function push(path: string, obj: string) {
}
}
// NOTE: adb reverse is not work by unavailable features support
async function connectToAndroidServer() {
try {
let inst = getAdbInstance();
if (!inst) {
console.warn('adb instance not found');
return;
}
const stream = await inst.transport.connect(env.PUBLIC_BREW_CONN_PORT);
const writer = stream.writable.getWriter();
const reader = stream.readable.getReader();
adbWriter.set(writer);
(async () => {
try {
while (true) {
const { value, done } = await reader.read();
if (done) break;
handleAdbPayload(new TextDecoder().decode(value));
}
} catch (e) {
console.error('read error', e);
} finally {
adbWriter.set(null);
}
})();
} catch (err) {
console.error('Connection failed. Suspect java running or not', err);
}
}
// logcat stream
// TODO: screen mirror