Skip to content

Commit

Permalink
Merge commit 'd91fef04ddcf7b70ad36dedf2d78e6ebfa9d2d7b' into ya-webad…
Browse files Browse the repository at this point in the history
…b-0.0.20

* commit 'd91fef04ddcf7b70ad36dedf2d78e6ebfa9d2d7b': (167 commits)
  chore: v0.0.20
  chore: fix typo
  chore: prepare for next release
  feat(web): start a new web app
  refactor(demo): code cleanups
  feat(credential): save keys in indexedDB to support workers
  feat(scrcpy): add `isSupported` method to`WebCodecDecoder`
  refactor: use ES private fields to replace TypeScript private accessors
  refactor: rename `AdbDaemonConnection` to `AdbDaemonDevice`
  chore: update ci node version
  chore: add sponsor badge
  chore: add sponsor badge
  fix(demo): update undici
  chore: update dependencies
  feat(adb): support connect to adb server (#549)
  chore(doc): fix typo
  chore(doc): fix typo
  fix(adb): fix tcp reverse tunnel on Android <8
  chore: fix build
  chore: fix build
  ...

# Conflicts:
#	.github/workflows/deploy.yml
#	.github/workflows/pull_request.yml
#	.github/workflows/test.yml
#	README.md
#	apps/demo/public/manifest.json
#	apps/demo/src/components/connect.tsx
#	apps/demo/src/pages/_app.tsx
#	apps/demo/src/pages/bug-report.tsx
#	apps/demo/src/pages/device-info.tsx
#	apps/demo/src/pages/file-manager.tsx
#	apps/demo/src/pages/framebuffer.tsx
#	apps/demo/src/pages/index.mdx
#	apps/demo/src/pages/install.tsx
#	apps/demo/src/pages/logcat.tsx
#	apps/demo/src/pages/packet-log.tsx
#	apps/demo/src/pages/power.tsx
#	apps/demo/src/pages/scrcpy.tsx
#	apps/demo/src/pages/shell.tsx
#	apps/demo/src/pages/tcpip.tsx
  • Loading branch information
nwtgck committed Jun 27, 2023
1 parent d91fef0 commit b688f17
Show file tree
Hide file tree
Showing 24 changed files with 338 additions and 116 deletions.
48 changes: 48 additions & 0 deletions .github/workflows/cloudflare-pages.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
name: Cloudflare Pages

on: [push]

jobs:
publish:
runs-on: ubuntu-20.04
permissions:
contents: read
deployments: write
statuses: write
steps:
- name: Checkout
uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18.x
- run: node common/scripts/install-run-rush.js install
- run: node common/scripts/install-run-rush.js build --verbose
- run: npx next export
working-directory: ./apps/demo

- name: Publish to Cloudflare Pages
id: cloudflare_pages_deploy
uses: cloudflare/pages-action@1
with:
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
projectName: piping-adb
directory: './apps/demo/out'
gitHubToken: ${{ secrets.GITHUB_TOKEN }}

- name: Add publish URL as commit status
uses: actions/github-script@v6
with:
script: |
// When "pull_request", context.payload.pull_request?.head.sha is expected SHA.
// (base: https://git.luolix.topmunity/t/github-sha-isnt-the-value-expected/17903/2)
const sha = context.payload.pull_request?.head.sha ?? context.sha;
await github.rest.repos.createCommitStatus({
owner: context.repo.owner,
repo: context.repo.repo,
context: 'Cloudflare Pages',
description: 'Cloudflare Pages deployment',
state: 'success',
sha,
target_url: "${{ steps.cloudflare_pages_deploy.outputs.url }}",
});
40 changes: 0 additions & 40 deletions .github/workflows/deploy.yml

This file was deleted.

32 changes: 32 additions & 0 deletions .github/workflows/netlify.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: Build and Deploy to Netlify

on:
push:
pull_request:

jobs:
build:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18.x
- run: node common/scripts/install-run-rush.js install
- run: node common/scripts/install-run-rush.js build --verbose
- run: npx next export
working-directory: ./apps/demo
- name: Deploy to Netlify
uses: nwtgck/actions-netlify@v1.2
with:
publish-dir: './apps/demo/out'
production-branch: main
github-token: ${{ secrets.GITHUB_TOKEN }}
deploy-message: "Deploy from GitHub Actions"
enable-pull-request-comment: false
enable-commit-comment: true
overwrites-pull-request-comment: true
env:
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
timeout-minutes: 1
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,5 @@ dts
esm
dist
*.tsbuildinfo

/.pnpm-store
23 changes: 22 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,25 @@
# Tango
# Piping ADB
[Android Debug Bridge (ADB)](https://developer.android.com/studio/command-line/adb) over [Piping Server](https://github.com/nwtgck/piping-server) on Web browser

## Usage
First, open adbd 5555 port on an Android device using `adb tcpip 5555` or `su 0 setprop service.adb.tcp.port 5555; su 0 stop adbd; su 0 start adbd`

Second, the device starts a tunneling over Piping Server in some way, equivalent to the following command:
```bash
curl -sSN https://ppng.io/mycspath | nc localhost 5555 | curl -sSNT - https://ppng.io/myscpath
```

- [Termux](https://termux.dev) is useful to run `curl` and `nc`.
- See "[Secure TCP tunnel from anywhere with curl and nc for single connection](https://dev.to/nwtgck/secure-tcp-tunnel-from-anywhere-with-curl-and-nc-for-single-connection-2k5i)" to know how the tunneling works.

Finally, open the following URL on a Chromium-based browser.
<https://piping-adb.nwtgck.org/?auto_connect&server=https://ppng.io&cs_path=mycspath&sc_path=myscpath>

## Acknowledgements

This project is highly based on [ya-webadb](https://github.com/yume-chan/ya-webadb). Thanks to the original author!

The following document is from the original README.

[![MIT license](https://img.shields.io/github/license/yume-chan/ya-webadb)](https://github.com/yume-chan/ya-webadb/blob/main/LICENSE)

Expand Down
3 changes: 2 additions & 1 deletion apps/demo/next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ module.exports = pipe(
},
},
withBundleAnalyzer,
withPwa,
// NOTE: `withPwa` tries to cache a GET response to Piping Server
// withPwa,
withMDX
);
20 changes: 20 additions & 0 deletions apps/demo/public/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"name": "Tango",
"short_name": "Tango",
"categories": [
"utilities",
"developer"
],
"description": "ADB in your browser",
"scope": "/",
"start_url": "/",
"background_color": "#ffffff",
"display": "standalone",
"icons": [
{
"src": "favicon-256.png",
"type": "image/png",
"sizes": "256x256"
}
]
}
74 changes: 74 additions & 0 deletions apps/demo/src/adb-piping-backend.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { AdbPacket, AdbPacketSerializeStream, type AdbDaemonDevice } from '@yume-chan/adb';
import {
Consumable, ConsumableWritableStream,
DuplexStreamFactory,
pipeFrom,
ReadableStream as YumeChanReadableStream,
StructDeserializeStream,
} from '@yume-chan/stream-extra';

export class AdbPipingBackend implements AdbDaemonDevice {
public readonly serial: string;
public readonly name: string | undefined = undefined;

public constructor(private params: {
csUrl: string,
scUrl: string,
csHeaders: Headers | undefined,
scHeaders: Headers | undefined,
}) {
this.serial = `piping_${params.csUrl}_${params.scUrl}`;
}

public async connect() {
const {readable: uploadReadableStream, writable: uploadWritableStream} = new TransformStream<Uint8Array>();
fetch(this.params.csUrl, {
method: "POST",
// headers: this.params.csHeaders,
body: uploadReadableStream,
duplex: "half",
} as RequestInit);

const scResReaderPromise = (async () => {
const scRes = await fetch(this.params.scUrl, {
headers: this.params.scHeaders,
});
return scRes.body!.getReader();
})()

const scReadableStream = new ReadableStream<Uint8Array>({
async pull(ctrl) {
const reader = await scResReaderPromise;
const result = await reader.read();
if (result.done) {
ctrl.close();
return;
}
ctrl.enqueue(result.value);
}
});

const duplex = new DuplexStreamFactory<
Uint8Array,
Consumable<Uint8Array>
>({
close: () => {
// TODO: impl
console.log("TODO: close");
},
});

const readable = duplex.wrapReadable(scReadableStream as YumeChanReadableStream);
const uploadWritableStreamWriter = uploadWritableStream.getWriter();
const writable = duplex.createWritable(new ConsumableWritableStream({
async write(chunk) {
await uploadWritableStreamWriter.write(chunk);
}
}));

return {
readable: readable.pipeThrough(new StructDeserializeStream(AdbPacket)),
writable: pipeFrom(writable, new AdbPacketSerializeStream()),
};
}
}
43 changes: 42 additions & 1 deletion apps/demo/src/components/connect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,22 @@ import { useCallback, useEffect, useMemo, useState } from "react";
import { GLOBAL_STATE } from "../state";
import { CommonStackTokens, Icons } from "../utils";

import {AdbPipingBackend} from "../adb-piping-backend";
import {useRouter} from "next/router";

const DropdownStyles = { dropdown: { width: "100%" } };

const CredentialStore = new AdbWebCredentialStore();

function urlJoin(baseUrl: string, path: string): string {
return baseUrl.replace(/\/$/, '') + "/" + path;
}

function _Connect(): JSX.Element | null {
const [selected, setSelected] = useState<AdbDaemonDevice | undefined>();
const router = useRouter();
const [connecting, setConnecting] = useState(false);
const [autoConnect, setAutoConnect] = useState(false);

const [usbSupported, setUsbSupported] = useState(true);
const [usbDeviceList, setUsbDeviceList] = useState<AdbDaemonDevice[]>([]);
Expand Down Expand Up @@ -170,6 +179,38 @@ function _Connect(): JSX.Element | null {
});
}, []);

const [pipingBackendList, setPipingBackendList] = useState<AdbPipingBackend[]>([]);

useEffect(() => {
const pipingSererUrl = router.query["server"] as string ?? "https://ppng.io";
const csPath = router.query["cs_path"] as string | undefined;
const scPath = router.query["sc_path"] as string | undefined;
if (csPath === undefined || scPath === undefined) {
return;
}
const headersString = router.query["headers"] as string | undefined;
const headers = headersString === undefined ? undefined : new Headers(JSON.parse(decodeURIComponent(headersString)));
const adbPipingBackend = new AdbPipingBackend({
csUrl: urlJoin(pipingSererUrl, csPath),
scUrl: urlJoin(pipingSererUrl, scPath),
scHeaders: headers,
csHeaders: headers,
});
setPipingBackendList([adbPipingBackend]);
// setSelectedBackend(adbPipingBackend);
setSelected(adbPipingBackend);
const autoConnectString = router.query["auto_connect"] as string | undefined;
setAutoConnect(autoConnectString === "" || autoConnectString === "true" || autoConnectString === "1");
}, [router]);

useEffect(() => {
if (autoConnect) {
console.log("auto connecting...", selected);
connect();
}
}, [autoConnect]);


const handleSelectedChange = (
e: React.FormEvent<HTMLDivElement>,
option?: IDropdownOption
Expand Down Expand Up @@ -270,7 +311,7 @@ function _Connect(): JSX.Element | null {
webSocketDeviceList,
tcpDeviceList
),
[usbDeviceList, webSocketDeviceList, tcpDeviceList]
[usbDeviceList, webSocketDeviceList, tcpDeviceList, pipingBackendList]
);

const deviceOptions = useMemo(() => {
Expand Down
2 changes: 1 addition & 1 deletion apps/demo/src/pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ function App({ Component, pageProps }: AppProps) {
/>

<StackItem grow>
<div className={classes.title}>Tango</div>
<div className={classes.title}>Piping ADB</div>
</StackItem>

<IconButton
Expand Down
2 changes: 1 addition & 1 deletion apps/demo/src/pages/bug-report.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ const BugReportPage: NextPage = () => {
return (
<Stack {...RouteStackProps}>
<Head>
<title>BugReport - Tango</title>
<title>BugReport - Piping ADB</title>
</Head>

<MessageBar messageBarType={MessageBarType.info}>
Expand Down
2 changes: 1 addition & 1 deletion apps/demo/src/pages/device-info.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ const DeviceInfo: NextPage = () => {
return (
<Stack {...RouteStackProps}>
<Head>
<title>Device Info - Tango</title>
<title>Device Info - Piping ADB</title>
</Head>

<MessageBar>
Expand Down
2 changes: 1 addition & 1 deletion apps/demo/src/pages/file-manager.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -871,7 +871,7 @@ const FileManager: NextPage = (): JSX.Element | null => {
return (
<Stack {...RouteStackProps}>
<Head>
<title>File Manager - Tango</title>
<title>File Manager - Piping ADB</title>
</Head>

<CommandBar items={state.menuItems} />
Expand Down
2 changes: 1 addition & 1 deletion apps/demo/src/pages/framebuffer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ const FrameBuffer: NextPage = (): JSX.Element | null => {
return (
<Stack {...RouteStackProps}>
<Head>
<title>Screen Capture - Tango</title>
<title>Screen Capture - Piping ADB</title>
</Head>

<CommandBar
Expand Down
Loading

1 comment on commit b688f17

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.