update electron android adb over browser
This commit is contained in:
parent
21109e4bf9
commit
f6295a9c2f
26 changed files with 1551 additions and 172 deletions
178
client-electron/src/pages/Android.tsx
Normal file
178
client-electron/src/pages/Android.tsx
Normal file
|
|
@ -0,0 +1,178 @@
|
|||
import { useShallow } from 'zustand/react/shallow'
|
||||
import { ADB_DEFAULT_DEVICE_FILTER } from '@yume-chan/adb-daemon-webusb'
|
||||
import AdbWebCredentialStore from '@yume-chan/adb-credential-web'
|
||||
import { Adb, AdbDaemonTransport } from '@yume-chan/adb'
|
||||
import useAdb from '../hooks/adb'
|
||||
import type { AdbScrcpyVideoStream } from '@yume-chan/adb-scrcpy'
|
||||
import { AdbScrcpyClient, AdbScrcpyOptions2_1 } from '@yume-chan/adb-scrcpy'
|
||||
import { WebCodecsDecoder } from '@yume-chan/scrcpy-decoder-webcodecs'
|
||||
import {
|
||||
ScrcpyLogLevel1_18,
|
||||
ScrcpyOptions2_1,
|
||||
ScrcpyVideoCodecId
|
||||
} from '@yume-chan/scrcpy'
|
||||
import { useState } from 'react'
|
||||
|
||||
const ScreenStream: React.FC<{ child: HTMLCanvasElement }> = ({
|
||||
child
|
||||
}: {
|
||||
child: HTMLCanvasElement
|
||||
}) => {
|
||||
return <div ref={ref => ref?.appendChild(child)}></div>
|
||||
}
|
||||
|
||||
const AndroidPage: React.FC = () => {
|
||||
const { manager, device, setDevice } = useAdb(
|
||||
useShallow(state => ({
|
||||
manager: state.manager,
|
||||
device: state.device,
|
||||
setDevice: state.setDevice
|
||||
}))
|
||||
)
|
||||
|
||||
const [decoder, setDecoder] = useState<WebCodecsDecoder | undefined>()
|
||||
const [client, setClient] = useState<AdbScrcpyClient | undefined>()
|
||||
const [adbClient, setAdbClient] = useState<Adb | undefined>(undefined)
|
||||
|
||||
function attachDevice() {
|
||||
manager
|
||||
?.requestDevice({
|
||||
filters: [
|
||||
{
|
||||
...ADB_DEFAULT_DEVICE_FILTER,
|
||||
vendorId: 1478
|
||||
}
|
||||
]
|
||||
})
|
||||
.then(selectedDevice => {
|
||||
if (!selectedDevice) {
|
||||
return
|
||||
} else {
|
||||
setDevice(selectedDevice)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function disConnectDevice() {
|
||||
device?.raw.forget()
|
||||
setDevice(undefined)
|
||||
}
|
||||
|
||||
// function reboot() {
|
||||
// device?.connect().then(connection => {
|
||||
// const credentialStore: AdbWebCredentialStore = new AdbWebCredentialStore()
|
||||
|
||||
// AdbDaemonTransport.authenticate({
|
||||
// serial: device?.serial,
|
||||
// connection,
|
||||
// credentialStore: credentialStore
|
||||
// }).then(transport => {
|
||||
// const adb: Adb = new Adb(transport)
|
||||
|
||||
// adb.power.reboot().then(() => {
|
||||
// console.log('reboot success')
|
||||
// })
|
||||
// })
|
||||
// })
|
||||
// }
|
||||
|
||||
async function scrcpyConnect() {
|
||||
// const url = new URL('../bin/scrcpy-server.bin', import.meta.url)
|
||||
// const server: ArrayBuffer = await fetch(url).then(res => res.arrayBuffer())
|
||||
|
||||
const connection = await device?.connect()
|
||||
|
||||
const credentialStore: AdbWebCredentialStore = new AdbWebCredentialStore()
|
||||
|
||||
const transport = await AdbDaemonTransport.authenticate({
|
||||
serial: device!.serial,
|
||||
connection: connection!,
|
||||
credentialStore: credentialStore
|
||||
})
|
||||
|
||||
const adb: Adb = new Adb(transport)
|
||||
setAdbClient(adb)
|
||||
// await AdbScrcpyClient.pushServer(
|
||||
// adb,
|
||||
// new ReadableStream({
|
||||
// start(controller) {
|
||||
// controller.enqueue(new Consumable(new Uint8Array(server)))
|
||||
// controller.close()
|
||||
// }
|
||||
// })
|
||||
// )
|
||||
|
||||
await adb.subprocess.spawn(
|
||||
'CLASSPATH=/data/local/tmp/scrcpy-server.jar app_process / com.genymobile.scrcpy.Server 2.1'
|
||||
)
|
||||
|
||||
const scrcpyOption = new ScrcpyOptions2_1({
|
||||
audio: false,
|
||||
maxFps: 60,
|
||||
control: false,
|
||||
video: true,
|
||||
logLevel: ScrcpyLogLevel1_18.Debug,
|
||||
videoCodec: 'h264',
|
||||
stayAwake: true,
|
||||
cleanup: true
|
||||
})
|
||||
const _client = await AdbScrcpyClient.start(
|
||||
adb,
|
||||
'/data/local/tmp/scrcpy-server.jar',
|
||||
'2.1',
|
||||
new AdbScrcpyOptions2_1(scrcpyOption)
|
||||
)
|
||||
|
||||
setClient(_client)
|
||||
}
|
||||
|
||||
async function scrcpyStream() {
|
||||
const videoStream: AdbScrcpyVideoStream | undefined =
|
||||
await client?.videoStream
|
||||
const _decoder = new WebCodecsDecoder(ScrcpyVideoCodecId.H264)
|
||||
videoStream?.stream.pipeTo(_decoder.writable)
|
||||
setDecoder(_decoder)
|
||||
}
|
||||
|
||||
function scrcpyDisconnect() {
|
||||
client?.close()
|
||||
setClient(undefined)
|
||||
setDecoder(undefined)
|
||||
}
|
||||
|
||||
function rebootDevice() {
|
||||
adbClient?.power.reboot()
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>This is Android Page!!!!!!!</h1>
|
||||
{device ? <h2>Device: {device.name}</h2> : <h2>No Device</h2>}
|
||||
<div className="flex flex-row">
|
||||
<button className="btn btn-primary" onClick={attachDevice}>
|
||||
Attach Device
|
||||
</button>
|
||||
<button className="btn btn-primary" onClick={disConnectDevice}>
|
||||
Disconnect Device
|
||||
</button>
|
||||
<button className="btn btn-primary" onClick={rebootDevice}>
|
||||
Reboot Device
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex flex-row">
|
||||
<button className="btn btn-primary" onClick={scrcpyConnect}>
|
||||
Scrcpy Connect
|
||||
</button>
|
||||
<button className="btn btn-primary" onClick={scrcpyStream}>
|
||||
Scrcpy Stream
|
||||
</button>
|
||||
<button className="btn btn-primary" onClick={scrcpyDisconnect}>
|
||||
Scrcpy Disconnect
|
||||
</button>
|
||||
</div>
|
||||
{decoder ? <ScreenStream child={decoder.renderer} /> : ''}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default AndroidPage
|
||||
|
|
@ -1,51 +1,15 @@
|
|||
import React from 'react'
|
||||
import googleLogo from '../assets/google-color.svg'
|
||||
|
||||
const Home: React.FC = () => {
|
||||
console.log(import.meta.env.TAOBIN_RECIPE_MANAGER_SERVER_URL)
|
||||
|
||||
const loginWithGoogle = () => {
|
||||
// if is web mode then use window.open
|
||||
// if is electron mode then use ipcRenderer.send to use deep link method
|
||||
if (import.meta.env.MODE === 'web') {
|
||||
// open new window and listen to message from window.opener
|
||||
const newWindow = window.open(
|
||||
import.meta.env.TAOBIN_RECIPE_MANAGER_SERVER_URL,
|
||||
'_blank',
|
||||
'width=500,height=600'
|
||||
)
|
||||
|
||||
// listen to message from new window
|
||||
window.addEventListener('message', event => {
|
||||
if (event.data.payload === 'loginSuccess') {
|
||||
// close new window
|
||||
newWindow?.close()
|
||||
|
||||
console.log(event.data)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
window.ipcRenderer.send('deeplink', 'http://127.0.0.1:5500/test.html')
|
||||
}
|
||||
}
|
||||
import { Link } from 'react-router-dom'
|
||||
|
||||
const HomePage: React.FC = () => {
|
||||
return (
|
||||
<div>
|
||||
<h1>This is Home Page!!!!!!!</h1>
|
||||
<button
|
||||
onClick={loginWithGoogle}
|
||||
className="bg-white px-4 py-2 border flex gap-2 border-slate-200 rounded-lg text-slate-700 hover:border-slate-400 hover:text-slate-900 hover:shadow transition duration-150"
|
||||
>
|
||||
<img
|
||||
className="w-6 h-6"
|
||||
src={googleLogo}
|
||||
alt="google logo"
|
||||
loading="eager"
|
||||
/>
|
||||
<span>Login with @forth.co.th Google account</span>
|
||||
<button className="btn btn-primary">
|
||||
<Link to="/android">Go to Android Page</Link>
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Home
|
||||
export default HomePage
|
||||
|
|
|
|||
85
client-electron/src/pages/Login.tsx
Normal file
85
client-electron/src/pages/Login.tsx
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
import { useNavigate } from 'react-router-dom'
|
||||
import googleLogo from '../assets/google-color.svg'
|
||||
import userAuthStore, { type UserInfo } from '../hooks/userAuth'
|
||||
|
||||
const LoginPage: React.FC = () => {
|
||||
const setUserInfo = userAuthStore(state => state.setUserInfo)
|
||||
const navigate = useNavigate()
|
||||
|
||||
const redirectUrl =
|
||||
new URLSearchParams(window.location.search).get('redirect') ?? '/'
|
||||
|
||||
const loginWithGoogle = () => {
|
||||
// if is web mode then use window.open
|
||||
// if is electron mode then use ipcRenderer.send to use deep link method
|
||||
if (window.electronRuntime) {
|
||||
window.ipcRenderer.send(
|
||||
'deeplink',
|
||||
import.meta.env.TAOBIN_RECIPE_MANAGER_SERVER_URL +
|
||||
'/auth/google?redirect_to=' +
|
||||
redirectUrl +
|
||||
'&kind=electron'
|
||||
)
|
||||
|
||||
window.ipcRenderer.on('loginSuccess', (_event, data) => {
|
||||
console.log(data)
|
||||
|
||||
setUserInfo(data satisfies UserInfo)
|
||||
navigate(redirectUrl)
|
||||
})
|
||||
} else {
|
||||
// open new window and listen to message from window.opener
|
||||
window.open(
|
||||
import.meta.env.TAOBIN_RECIPE_MANAGER_SERVER_URL +
|
||||
'/auth/google?redirect_to=' +
|
||||
redirectUrl,
|
||||
'_blank',
|
||||
'width=500,height=600'
|
||||
)
|
||||
|
||||
// listen to message from new window
|
||||
window.addEventListener('message', event => {
|
||||
if (event.data.payload === 'loginSuccess') {
|
||||
// const { access_token, max_age, refresh_token } = event.data.data
|
||||
|
||||
// setPassword(
|
||||
// import.meta.env.TAOBIN_RECIPE_MANAGER_KEY_CHAIN_SERVICE_NAME,
|
||||
// import.meta.env
|
||||
// .TAOBIN_RECIPE_MANAGER_KEY_CHAIN_ACCOUNT_ACCESS_TOKEN,
|
||||
// access_token + ';' + max_age
|
||||
// )
|
||||
|
||||
// setPassword(
|
||||
// import.meta.env.TAOBIN_RECIPE_MANAGER_KEY_CHAIN_SERVICE_NAME,
|
||||
// import.meta.env
|
||||
// .TAOBIN_RECIPE_MANAGER_KEY_CHAIN_ACCOUNT_REFRESH_TOKEN,
|
||||
// refresh_token
|
||||
// )
|
||||
|
||||
setUserInfo(event.data.data satisfies UserInfo)
|
||||
|
||||
navigate(redirectUrl)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center h-screen">
|
||||
<button
|
||||
onClick={loginWithGoogle}
|
||||
className="bg-white px-4 py-2 border flex gap-2 border-slate-200 rounded-lg text-slate-700 hover:border-slate-400 hover:text-slate-900 hover:shadow transition duration-150"
|
||||
>
|
||||
<img
|
||||
className="w-6 h-6"
|
||||
src={googleLogo}
|
||||
alt="google logo"
|
||||
loading="eager"
|
||||
/>
|
||||
<span>Login with @forth.co.th Google account</span>
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default LoginPage
|
||||
Loading…
Add table
Add a link
Reference in a new issue