425 lines
11 KiB
Svelte
425 lines
11 KiB
Svelte
|
|
<script lang="ts">
|
||
|
|
import { Button, type ButtonVariant } from './ui/button';
|
||
|
|
import * as Card from './ui/card/index';
|
||
|
|
import Spinner from '$lib/components/ui/spinner/spinner.svelte';
|
||
|
|
import Checkbox from '$lib/components/ui/checkbox/checkbox.svelte';
|
||
|
|
import Label from '$lib/components/ui/label/label.svelte';
|
||
|
|
|
||
|
|
import { LockIcon, UnlockIcon, Circle, AlertCircleIcon } from '@lucide/svelte/icons';
|
||
|
|
import * as adb from '$lib/core/adb/adb';
|
||
|
|
import { machineInfoStore } from '$lib/core/stores/machineInfoStore';
|
||
|
|
import { toast } from 'svelte-sonner';
|
||
|
|
import { handleIncomingMessages } from '$lib/core/handlers/messageHandler';
|
||
|
|
import { auth as authStore } from '$lib/core/stores/auth';
|
||
|
|
import { get } from 'svelte/store';
|
||
|
|
import { AdbDaemonWebUsbDeviceManager } from '@yume-chan/adb-daemon-webusb';
|
||
|
|
import AdbWebCredentialStore from '@yume-chan/adb-credential-web';
|
||
|
|
import { onMount } from 'svelte';
|
||
|
|
import { deviceCredentialManager } from '$lib/core/adb/deviceCredManager';
|
||
|
|
import { file } from 'zod/mini';
|
||
|
|
|
||
|
|
let { enableComponent = true } = $props();
|
||
|
|
|
||
|
|
// connection button states
|
||
|
|
let connectionButtonText = $state('Connect');
|
||
|
|
let connectionButtonDisable = $state(false);
|
||
|
|
let connectionButtonVariant: ButtonVariant = $state('default');
|
||
|
|
|
||
|
|
let connectDeviceOk = $state(false);
|
||
|
|
let hasStoredDevice = $state(false);
|
||
|
|
|
||
|
|
let openAppBrewWhenConnected = $state(true);
|
||
|
|
let hasOpenedBrewOnce = $state(false);
|
||
|
|
|
||
|
|
// progress
|
||
|
|
let showLoadProgress = $state(false);
|
||
|
|
//
|
||
|
|
let recipe: any | undefined = $state(undefined);
|
||
|
|
|
||
|
|
let machineStatus: string = $state('Ok');
|
||
|
|
// machineInfoStore.subscribe((mInfo) => {});
|
||
|
|
|
||
|
|
const essentialFiles = ['/sdcard/coffeevending/versions/'];
|
||
|
|
async function loadEssentialFiles() {
|
||
|
|
showLoadProgress = true;
|
||
|
|
machineStatus = 'Loading infos ...';
|
||
|
|
|
||
|
|
let instance = adb.getAdbInstance();
|
||
|
|
if (instance) {
|
||
|
|
// check country
|
||
|
|
let country = await adb.pull('/sdcard/coffeevending/country/short');
|
||
|
|
machineStatus = `Found country: ${country}`;
|
||
|
|
// check dev
|
||
|
|
let devMode = await adb.pull('/sdcard/coffeevending/CURR_TEST');
|
||
|
|
if (devMode?.includes('1')) {
|
||
|
|
machineStatus = `Dev mode enabled`;
|
||
|
|
} else {
|
||
|
|
machineStatus = `Dev mode disabled`;
|
||
|
|
}
|
||
|
|
// check .bid
|
||
|
|
let boxid = await adb.pull('/sdcard/coffeevending/.bid');
|
||
|
|
if (boxid) {
|
||
|
|
machineStatus = `Box id is ${boxid}`;
|
||
|
|
} else {
|
||
|
|
machineStatus = 'No box id';
|
||
|
|
}
|
||
|
|
|
||
|
|
machineInfoStore.set({
|
||
|
|
boxId: boxid,
|
||
|
|
versions: {
|
||
|
|
firmware: '',
|
||
|
|
brew: '',
|
||
|
|
xmlengine: '',
|
||
|
|
netcore: '',
|
||
|
|
devbox: ''
|
||
|
|
},
|
||
|
|
devMode: devMode?.includes('1') ?? false,
|
||
|
|
country: country ?? '',
|
||
|
|
status: '',
|
||
|
|
errors: []
|
||
|
|
});
|
||
|
|
|
||
|
|
handleIncomingMessages(
|
||
|
|
JSON.stringify({
|
||
|
|
type: 'chat',
|
||
|
|
payload: `${new Date().toLocaleTimeString()}: ${get(authStore)?.displayName} has connected to ${boxid}`
|
||
|
|
})
|
||
|
|
);
|
||
|
|
} else {
|
||
|
|
machineStatus = 'Instance lost, try disconnect and re-connect again';
|
||
|
|
toast.error('Unexpected Error');
|
||
|
|
}
|
||
|
|
showLoadProgress = false;
|
||
|
|
}
|
||
|
|
|
||
|
|
async function openBrewApp() {
|
||
|
|
try {
|
||
|
|
let instance = adb.getAdbInstance();
|
||
|
|
if (instance) {
|
||
|
|
try {
|
||
|
|
// bypass
|
||
|
|
await adb.executeCmd('echo -n hurr > /sdcard/coffeevending/ignore_pass');
|
||
|
|
} catch (e) {}
|
||
|
|
|
||
|
|
let result = await adb.executeCmd(
|
||
|
|
'am start -n com.forthvending.coffeemain/com.forthvending.coffeemain.MainActivity'
|
||
|
|
);
|
||
|
|
if (result?.output) {
|
||
|
|
toast.success('Open app success!');
|
||
|
|
machineStatus = 'open app success, check the screen and put the password';
|
||
|
|
} else if (result?.error) {
|
||
|
|
// case usb connection cutoff
|
||
|
|
if (result.error === 'ExactReadableEndedError') {
|
||
|
|
toast.warning('Connection unstable');
|
||
|
|
machineStatus = 'app maybe opened, check the screen';
|
||
|
|
} else {
|
||
|
|
throw new Error(`Exit ${result.exitCode}. ${result.error}`);
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
throw new Error('Instance not found or error while executing');
|
||
|
|
}
|
||
|
|
|
||
|
|
hasOpenedBrewOnce = true;
|
||
|
|
|
||
|
|
try {
|
||
|
|
// bypass
|
||
|
|
await adb.executeCmd('echo -n hurr > /sdcard/coffeevending/ignore_pass');
|
||
|
|
} catch (e) {}
|
||
|
|
|
||
|
|
setTimeout(async () => {
|
||
|
|
try {
|
||
|
|
// bypass
|
||
|
|
await adb.executeCmd('input tap 336 795');
|
||
|
|
} catch (e) {}
|
||
|
|
}, 3000);
|
||
|
|
}
|
||
|
|
} catch (e: any) {
|
||
|
|
machineStatus = 'Cannot open brew app';
|
||
|
|
toast.error('Error while trying to open brew app, please check the screen. ', {
|
||
|
|
description: e.toString()
|
||
|
|
});
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
async function testPushPullFile() {
|
||
|
|
try {
|
||
|
|
let instance = adb.getAdbInstance();
|
||
|
|
if (instance) {
|
||
|
|
await adb.push(
|
||
|
|
'/sdcard/coffeevending/test.json',
|
||
|
|
JSON.stringify({ test: new Date().toLocaleTimeString() })
|
||
|
|
);
|
||
|
|
|
||
|
|
let result = await adb.pull('/sdcard/coffeevending/test.json');
|
||
|
|
|
||
|
|
if (result) {
|
||
|
|
if (result === '') {
|
||
|
|
console.log('push pull not ok, get empty');
|
||
|
|
} else {
|
||
|
|
console.log('push pull ok', result);
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
console.log('push pull not ok', result);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
} catch (error) {
|
||
|
|
console.log('test push file failed', error);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
async function connectAdb() {
|
||
|
|
connectionButtonText = '...';
|
||
|
|
// lock away no spam
|
||
|
|
connectionButtonDisable = true;
|
||
|
|
connectionButtonVariant = 'outline';
|
||
|
|
|
||
|
|
try {
|
||
|
|
if (!AdbDaemonWebUsbDeviceManager.BROWSER) {
|
||
|
|
throw new Error('WebUSB not supported, try using fallback or different browser');
|
||
|
|
}
|
||
|
|
|
||
|
|
await adb.connnectViaWebUSB();
|
||
|
|
let instance = adb.getAdbInstance();
|
||
|
|
|
||
|
|
if (instance) {
|
||
|
|
await loadEssentialFiles();
|
||
|
|
if (openAppBrewWhenConnected) await openBrewApp();
|
||
|
|
}
|
||
|
|
} catch (e: any) {
|
||
|
|
if (e.message === 'CREDENTIAL_EXPIRED') {
|
||
|
|
try {
|
||
|
|
await deviceCredentialManager.clearAllCredentials();
|
||
|
|
hasStoredDevice = false;
|
||
|
|
} catch (ignored) {}
|
||
|
|
}
|
||
|
|
|
||
|
|
console.log('error on quick adb: ', e);
|
||
|
|
toast.error(`Machine Connection Error`, {
|
||
|
|
description: e.toString()
|
||
|
|
});
|
||
|
|
connectionButtonText = 'Retry';
|
||
|
|
connectionButtonVariant = 'default';
|
||
|
|
connectDeviceOk = false;
|
||
|
|
}
|
||
|
|
connectionButtonDisable = false;
|
||
|
|
}
|
||
|
|
|
||
|
|
async function disconnectAdb() {
|
||
|
|
await adb.disconnect();
|
||
|
|
connectionButtonText = 'Connect';
|
||
|
|
connectionButtonVariant = 'default';
|
||
|
|
|
||
|
|
connectDeviceOk = false;
|
||
|
|
|
||
|
|
handleIncomingMessages(
|
||
|
|
JSON.stringify({
|
||
|
|
type: 'chat',
|
||
|
|
payload: `${new Date().toLocaleTimeString()}: ${get(authStore)?.displayName} has disconnected!`
|
||
|
|
})
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
function checkDeviceConnection() {
|
||
|
|
try {
|
||
|
|
let instance = adb.getAdbInstance();
|
||
|
|
|
||
|
|
if (instance) {
|
||
|
|
// ready
|
||
|
|
connectionButtonText = 'Disconnect';
|
||
|
|
connectionButtonVariant = 'destructive';
|
||
|
|
|
||
|
|
connectDeviceOk = true;
|
||
|
|
} else {
|
||
|
|
connectionButtonText = 'Connect';
|
||
|
|
connectionButtonVariant = 'default';
|
||
|
|
|
||
|
|
connectDeviceOk = false;
|
||
|
|
}
|
||
|
|
} catch (e: any) {
|
||
|
|
console.log('error on quick adb: ', e);
|
||
|
|
toast.error(`Machine Connection Error`, {
|
||
|
|
description: e.toString()
|
||
|
|
});
|
||
|
|
connectionButtonText = 'Retry';
|
||
|
|
connectionButtonVariant = 'default';
|
||
|
|
connectDeviceOk = false;
|
||
|
|
}
|
||
|
|
connectionButtonDisable = false;
|
||
|
|
}
|
||
|
|
|
||
|
|
async function checkStoredCredentials() {
|
||
|
|
try {
|
||
|
|
if (!AdbDaemonWebUsbDeviceManager.BROWSER) {
|
||
|
|
hasStoredDevice = false;
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
const devices = await AdbDaemonWebUsbDeviceManager.BROWSER.getDevices();
|
||
|
|
if (!devices || devices.length === 0) {
|
||
|
|
hasStoredDevice = false;
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
const credentialStore = new AdbWebCredentialStore();
|
||
|
|
let hasKeys = false;
|
||
|
|
|
||
|
|
try {
|
||
|
|
for await (const key of credentialStore.iterateKeys()) {
|
||
|
|
hasKeys = true;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
} catch (e) {
|
||
|
|
console.log('check stored error', e);
|
||
|
|
}
|
||
|
|
|
||
|
|
hasStoredDevice = devices.length > 0 && hasKeys;
|
||
|
|
} catch (e) {
|
||
|
|
console.error('check stored error', e);
|
||
|
|
hasStoredDevice = false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
async function tryAutoConnect(): Promise<boolean> {
|
||
|
|
try {
|
||
|
|
connectionButtonText = '...';
|
||
|
|
// lock away no spam
|
||
|
|
connectionButtonDisable = true;
|
||
|
|
connectionButtonVariant = 'outline';
|
||
|
|
|
||
|
|
if (!AdbDaemonWebUsbDeviceManager.BROWSER) {
|
||
|
|
throw new Error('WebUSB not supported, try using fallback or different browser');
|
||
|
|
}
|
||
|
|
|
||
|
|
const devices = await AdbDaemonWebUsbDeviceManager.BROWSER?.getDevices();
|
||
|
|
if (!devices || devices.length == 0) {
|
||
|
|
throw new Error('No device found');
|
||
|
|
}
|
||
|
|
|
||
|
|
if (devices.length > 1) {
|
||
|
|
throw new Error('Too many connected devices');
|
||
|
|
}
|
||
|
|
|
||
|
|
const device = devices[0];
|
||
|
|
const credStore = new AdbWebCredentialStore();
|
||
|
|
|
||
|
|
try {
|
||
|
|
await adb.connectDeviceByCred(device, credStore);
|
||
|
|
return true;
|
||
|
|
} catch (e: any) {
|
||
|
|
if (e.message === 'CREDENTIAL_EXPIRED') {
|
||
|
|
try {
|
||
|
|
await deviceCredentialManager.clearAllCredentials();
|
||
|
|
hasStoredDevice = false;
|
||
|
|
} catch (ignored) {}
|
||
|
|
}
|
||
|
|
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
} catch (e: any) {
|
||
|
|
console.log('error on auto connect adb: ', e);
|
||
|
|
toast.error(`Machine Connection Error`, {
|
||
|
|
description: e.toString()
|
||
|
|
});
|
||
|
|
connectionButtonText = 'Connect';
|
||
|
|
connectionButtonVariant = 'default';
|
||
|
|
connectDeviceOk = false;
|
||
|
|
}
|
||
|
|
|
||
|
|
connectionButtonDisable = false;
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
// update every 1s
|
||
|
|
setInterval(async function () {
|
||
|
|
checkDeviceConnection();
|
||
|
|
}, 1000);
|
||
|
|
|
||
|
|
onMount(async () => {
|
||
|
|
await checkStoredCredentials();
|
||
|
|
await tryAutoConnect();
|
||
|
|
});
|
||
|
|
</script>
|
||
|
|
|
||
|
|
<div class="p-4">
|
||
|
|
<Card.Root class="h-full bg-muted/50">
|
||
|
|
<Card.Header>
|
||
|
|
<Card.Title>Machine Shortcuts</Card.Title>
|
||
|
|
<Card.Description>Shortcuts for machine i.e. connect or hotfix</Card.Description>
|
||
|
|
{#if enableComponent}
|
||
|
|
<Card.Action>
|
||
|
|
<Button
|
||
|
|
variant={connectionButtonVariant}
|
||
|
|
disabled={connectionButtonDisable}
|
||
|
|
onclick={connectDeviceOk ? disconnectAdb : connectAdb}>{connectionButtonText}</Button
|
||
|
|
>
|
||
|
|
</Card.Action>
|
||
|
|
{/if}
|
||
|
|
</Card.Header>
|
||
|
|
<Card.Content>
|
||
|
|
{#if enableComponent}
|
||
|
|
<!-- -->
|
||
|
|
<div class="items-center space-y-8">
|
||
|
|
<!-- <div class="flex w-full items-center gap-2">
|
||
|
|
<UnlockIcon />
|
||
|
|
<span>This feature is enabled </span>
|
||
|
|
</div> -->
|
||
|
|
|
||
|
|
<div class="flex w-full items-center justify-between gap-2">
|
||
|
|
<p>
|
||
|
|
Device: {connectDeviceOk ? 'Online' : 'Offline'}
|
||
|
|
</p>
|
||
|
|
<Circle
|
||
|
|
class=""
|
||
|
|
color={connectDeviceOk ? 'green' : 'red'}
|
||
|
|
fill={connectDeviceOk ? 'green' : 'red'}
|
||
|
|
size={16}
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
<div class="flex w-full items-start gap-2">
|
||
|
|
<Checkbox
|
||
|
|
id="open_brew_now"
|
||
|
|
checked={openAppBrewWhenConnected}
|
||
|
|
onCheckedChange={() => {
|
||
|
|
openAppBrewWhenConnected = !openAppBrewWhenConnected;
|
||
|
|
}}
|
||
|
|
/>
|
||
|
|
<div class="grid gap-2">
|
||
|
|
<Label for="open_brew_now">Show brew app when connected</Label>
|
||
|
|
<p class="text-sm text-muted-foreground">
|
||
|
|
Immediately try to open brew app when first connected
|
||
|
|
</p>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- open app brew manual -->
|
||
|
|
<div class="flex w-full items-center gap-2">
|
||
|
|
<Button variant="default" onclick={openBrewApp} disabled={!connectDeviceOk}
|
||
|
|
>Open Brew App</Button
|
||
|
|
>
|
||
|
|
<Button variant="default" disabled={true}>Refresh Infos</Button>
|
||
|
|
|
||
|
|
<!-- test push file -->
|
||
|
|
<Button variant="default" onclick={testPushPullFile} disabled={!connectDeviceOk}
|
||
|
|
>Test Push</Button
|
||
|
|
>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
{:else}
|
||
|
|
<!-- show lock -->
|
||
|
|
<div class="flex w-full items-center justify-center gap-2">
|
||
|
|
<LockIcon /> <span>This feature is not enabled </span>
|
||
|
|
</div>
|
||
|
|
{/if}
|
||
|
|
</Card.Content>
|
||
|
|
<Card.Footer>
|
||
|
|
<!-- Display status -->
|
||
|
|
{#if connectDeviceOk}
|
||
|
|
{#if showLoadProgress}
|
||
|
|
<Spinner />
|
||
|
|
{/if}
|
||
|
|
<p class="mx-4 font-mono text-sm text-muted-foreground">{machineStatus}</p>
|
||
|
|
{/if}
|
||
|
|
</Card.Footer>
|
||
|
|
</Card.Root>
|
||
|
|
</div>
|