├── .gitignore
├── LICENSE
├── README.md
├── build_dist.sh
├── index.html
├── package-lock.json
├── package.json
├── plugins
├── binary-loader.js
└── md-loader.js
├── postcss.config.cjs
├── src
├── Extension.vue
├── Nav.vue
├── app
│ └── background.js
├── assets
│ ├── favicon-32x32.png
│ ├── favicon.ico
│ ├── fonts
│ │ ├── RobotoMono-Regular.ttf
│ │ ├── XRXV3I6Li01BKofIMeaBXso.woff2
│ │ ├── XRXV3I6Li01BKofINeaB.woff2
│ │ ├── XRXV3I6Li01BKofIO-aBXso.woff2
│ │ ├── XRXV3I6Li01BKofIOOaBXso.woff2
│ │ ├── XRXV3I6Li01BKofIOuaBXso.woff2
│ │ └── font.css
│ ├── icon_64.png
│ ├── logo.png
│ └── scrcpy-server-v1.24.jar
├── components
│ ├── FileManager.vue
│ ├── Howto.vue
│ ├── Phone.vue
│ ├── Scrcpy.vue
│ ├── credential-ext.js
│ ├── fsutil.js
│ └── scrcpyclient.js
├── docs
│ ├── enabledebug.md
│ ├── privacy.md
│ └── terms.md
├── extension.js
├── main.js
├── manifest.json
└── style.css
├── tailwind.config.cjs
├── vite.config.js
└── vite.extension.config.js
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.zip
4 | *.log
5 | npm-debug.log*
6 | yarn-debug.log*
7 | yarn-error.log*
8 | pnpm-debug.log*
9 | lerna-debug.log*
10 |
11 | node_modules
12 | dist
13 | dist-ssr
14 | *.local
15 |
16 | # Editor directories and files
17 | .vscode/*
18 | !.vscode/extensions.json
19 | .idea
20 | .DS_Store
21 | *.suo
22 | *.ntvs*
23 | *.njsproj
24 | *.sln
25 | *.sw?
26 | sync_www.sh
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Android in web browser
2 | =======
3 | Using Android phone in web browsers. ([Apache License 2.0](https://github.com/Genymobile/scrcpy/blob/master/LICENSE))
4 |
5 |
6 | Based:
7 | Vue3 + Vite + TailwindCSS + ya-webadb + scrcpy
8 |
9 |
10 | [Online Demo](https://browserlify.com/?from=github)
11 |
12 | Also availabled in [chrome webstore](https://chrome.google.com/webstore/detail/phone-on-web-browserlifyc/gaibhhiogjpncmcehohlmcikfgbcacbl)
13 |
14 |
15 | ## Features
16 | * Screen mirroring and controling device
17 | * File Management
18 | * Screen Capture
19 |
20 |
21 | ## Enabled USB Debugging in first
22 | * Enabled android phone developer mode
23 | * Enabled USB Debugging mode
24 |
25 | ## How to start
26 | ```shell
27 | # Install depends
28 | npm install --force
29 |
30 | # run local test
31 | npm run dev
32 |
33 | # Bulid for release
34 | npm run build
35 | ```
36 |
37 | ## Roadmap
38 | * Keyboard mapping
39 | * Screen sharing
40 | * Multi screen in one tab
41 |
42 | ## Credits
43 | * Android Debug Bridge (ADB) for Web Browsers [ya-webadb](https://github.com/yume-chan/ya-webadb)
44 | * Google for [ADB](https://android.googlesource.com/platform/packages/modules/adb) ([Apache License 2.0](./adb.NOTICE))
45 | * Romain Vimont for [Scrcpy](https://github.com/Genymobile/scrcpy) ([Apache License 2.0](https://github.com/Genymobile/scrcpy/blob/master/LICENSE))
--------------------------------------------------------------------------------
/build_dist.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | set -x
3 | echo 'build dist ... ' $@
4 | rm -Rf dist_$@-$npm_package_version.zip
5 | cd dist
6 | zip -r ../dist_$@-$npm_package_version.zip *
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
199 |
--------------------------------------------------------------------------------
/src/components/credential-ext.js:
--------------------------------------------------------------------------------
1 | // cspell: ignore RSASSA
2 |
3 | import { calculateBase64EncodedLength, calculatePublicKey, calculatePublicKeyLength, decodeBase64, decodeUtf8, encodeBase64 } from "@yume-chan/adb";
4 |
5 | export default class AdbWebCredentialExtStore {
6 |
7 | constructor(localStorageKey = 'private-key') {
8 | this.localStorageKey = localStorageKey;
9 | this._vals = {}
10 | }
11 |
12 | setItem(key, value) {
13 | return new Promise((solve, reject) => {
14 | let vals = {}
15 | vals[key] = value
16 |
17 | if (chrome.storage) {
18 | chrome.storage.local.set(vals, () => {
19 | solve()
20 | });
21 | } else {
22 | this._vals[key] = value
23 | solve()
24 | }
25 | })
26 | }
27 |
28 | getItem(key) {
29 | return new Promise((solve, reject) => {
30 | if (chrome.storage) {
31 | chrome.storage.local.get(key, (vals) => {
32 | if (vals[key]) {
33 | solve(vals[key])
34 | } else {
35 | solve(undefined)
36 | }
37 | });
38 | } else {
39 | solve(this._vals[key])
40 | }
41 | })
42 | }
43 |
44 | async *iterateKeys() {
45 | const privateKey = await this.getItem(this.localStorageKey);
46 | if (privateKey) {
47 | yield decodeBase64(privateKey);
48 | }
49 | }
50 |
51 | async generateKey() {
52 | const { privateKey: cryptoKey } = await crypto.subtle.generateKey(
53 | {
54 | name: 'RSASSA-PKCS1-v1_5',
55 | modulusLength: 2048,
56 | // 65537
57 | publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
58 | hash: 'SHA-1',
59 | },
60 | true,
61 | ['sign', 'verify']
62 | );
63 |
64 | const privateKey = new Uint8Array(await crypto.subtle.exportKey('pkcs8', cryptoKey));
65 | await this.setItem(this.localStorageKey, decodeUtf8(encodeBase64(privateKey)));
66 |
67 | // The authentication module in core doesn't need public keys.
68 | // It will generate the public key from private key every time.
69 | // However, maybe there are people want to manually put this public key onto their device,
70 | // so also save the public key for their convenience.
71 | const publicKeyLength = calculatePublicKeyLength();
72 | const [publicKeyBase64Length] = calculateBase64EncodedLength(publicKeyLength);
73 | const publicKeyBuffer = new Uint8Array(publicKeyBase64Length);
74 | calculatePublicKey(privateKey, publicKeyBuffer);
75 | encodeBase64(
76 | publicKeyBuffer.subarray(0, publicKeyLength),
77 | publicKeyBuffer
78 | );
79 | await this.setItem(this.localStorageKey + '.pub', decodeUtf8(publicKeyBuffer));
80 |
81 | return privateKey;
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/components/fsutil.js:
--------------------------------------------------------------------------------
1 | import {
2 | ChunkStream,
3 | ADB_SYNC_MAX_PACKET_SIZE,
4 | LinuxFileType,
5 | WrapReadableStream
6 | } from "@yume-chan/adb";
7 |
8 | export class FileManager {
9 | constructor(device) {
10 | this.device = device
11 | }
12 |
13 | joinPath(cwd, name) {
14 | if (cwd[cwd.length - 1] == '/') {
15 | return cwd + name
16 | } else {
17 | return cwd + '/' + name
18 | }
19 | }
20 |
21 | async openDir(currentPath) {
22 | let cwd = currentPath || '/'
23 | const sync = await this.device.sync();
24 | let filelist = []
25 | if (cwd != '/') {
26 | let vals = cwd.split('/')
27 | filelist.push({
28 | type: LinuxFileType.Directory,
29 | name: '..',
30 | path: vals.slice(0, vals.length - 1).join('/')
31 | })
32 | }
33 |
34 | try {
35 | for await (const entry of sync.opendir(cwd)) {
36 | if (entry.name === '.' || entry.name === '..') {
37 | continue;
38 | }
39 |
40 | filelist.push({
41 | name: entry.name,
42 | path: this.joinPath(cwd, entry.name),
43 | size: Number(entry.size),
44 | type: entry.type,
45 | mtime: Number(entry.mtime)
46 | })
47 | }
48 | } catch (e) {
49 | sync.dispose()
50 | throw e
51 | }
52 | try {
53 | for (let idx = 0; idx < filelist.length; idx++) {
54 | const item = filelist[idx];
55 | if (item.type == LinuxFileType.Link) {
56 | const isDir = await sync.isDirectory(item.path)
57 | item.type = isDir ? LinuxFileType.Directory : LinuxFileType.File
58 | filelist[idx] = item
59 | }
60 | }
61 | } catch (e) {
62 | // ignore
63 | }
64 |
65 | sync.dispose()
66 | return filelist
67 | }
68 |
69 | async openFile(item) {
70 | if (item.type == LinuxFileType.Directory) { return }
71 | const sync = await this.device.sync();
72 | let data = []
73 |
74 | try {
75 | let stream = await sync.read(item.path)
76 | await stream.pipeTo(new WritableStream({
77 | write: (v) => {
78 | data.push(v)
79 | },
80 | }))
81 | } catch (e) {
82 | sync.dispose()
83 | throw e
84 | }
85 | sync.dispose()
86 | return new Blob(data)
87 | }
88 |
89 | async deleteFile(itemPath) {
90 | await this.device.rm(itemPath);
91 | }
92 |
93 | async uploadFile(itemPath, file) {
94 | const sync = await this.device.sync();
95 | try {
96 | await new WrapReadableStream(file.stream())
97 | .pipeThrough(new ChunkStream(ADB_SYNC_MAX_PACKET_SIZE))
98 | .pipeTo(sync.write(
99 | itemPath,
100 | (LinuxFileType.File << 12) | 0o666,
101 | file.lastModified / 1000,
102 | ));
103 | } catch (e) {
104 | sync.dispose()
105 | throw e
106 | }
107 | sync.dispose()
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/src/components/scrcpyclient.js:
--------------------------------------------------------------------------------
1 | import {
2 | Adb,
3 | ChunkStream,
4 | WrapReadableStream,
5 | InspectStream,
6 | ADB_SYNC_MAX_PACKET_SIZE
7 | } from "@yume-chan/adb";
8 |
9 | import {
10 | DEFAULT_SERVER_PATH,
11 | ScrcpyClient,
12 | ScrcpyOptions1_24,
13 | CodecOptions,
14 | pushServer,
15 | ScrcpyLogLevel,
16 | WebCodecsDecoder,
17 | ScrcpyVideoOrientation,
18 | AndroidKeyCode,
19 | AndroidKeyEventAction,
20 | AndroidMotionEventAction
21 | } from "@yume-chan/scrcpy";
22 |
23 | import AdbWebUsbBackend from "@yume-chan/adb-backend-webusb";
24 | import AdbWebCredentialStore from "@yume-chan/adb-credential-web";
25 | import AdbWebCredentialExtStore from "./credential-ext"
26 | import Struct from "@yume-chan/struct"
27 |
28 | import { FileManager } from "./fsutil";
29 |
30 | const SCRCPY_SERVER_VERSION = "1.24";
31 | /*
32 | import SCRCPY_SERVER_URL from "../assets/scrcpy-server-v1.24.jar";
33 | await fetch(SCRCPY_SERVER_URL)
34 | .then(response => new WrapReadableStream(response.body))
35 | .then(stream => stream.pipeTo(pushServer(this.device)));
36 | */
37 |
38 | import SCRCPY_SERVER_BIN from "../assets/scrcpy-server-v1.24.jar?binary";
39 |
40 | const SCREEN_POWER_MODE_OFF = 0;
41 | const SCREEN_POWER_MODE_NORMAL = 2;
42 | const SetScreenPowerMode = 10;
43 |
44 | function clamp(value, min, max) {
45 | if (value < min) {
46 | return min;
47 | }
48 |
49 | if (value > max) {
50 | return max;
51 | }
52 | return value;
53 | }
54 | const ScrcpySetPowerModeControlMessage = new Struct()
55 | .uint8('type')
56 | .uint8('mode')
57 |
58 | class KeyRepeater {
59 | constructor(key, client, delay = 0, interval = 0) {
60 | this.key = key;
61 | this.client = client;
62 |
63 | this.delay = delay;
64 | this.interval = interval;
65 | }
66 |
67 | async press() {
68 | await this.client.injectKeyCode({
69 | action: AndroidKeyEventAction.Down,
70 | keyCode: this.key,
71 | repeat: 0,
72 | metaState: 0,
73 | });
74 |
75 | if (this.delay === 0) {
76 | return;
77 | }
78 |
79 | const timeoutId = setTimeout(async () => {
80 | await this.client.injectKeyCode({
81 | action: AndroidKeyEventAction.Down,
82 | keyCode: this.key,
83 | repeat: 1,
84 | metaState: 0,
85 | });
86 |
87 | if (this.interval === 0) {
88 | return;
89 | }
90 |
91 | const intervalId = setInterval(async () => {
92 | await this.client.injectKeyCode({
93 | action: AndroidKeyEventAction.Down,
94 | keyCode: this.key,
95 | repeat: 1,
96 | metaState: 0,
97 | });
98 | }, this.interval);
99 | this.onRelease = () => clearInterval(intervalId);
100 | }, this.delay);
101 | this.onRelease = () => clearTimeout(timeoutId);
102 | }
103 |
104 | async release() {
105 | if (this.onRelease) {
106 | this.onRelease();
107 | }
108 |
109 | await this.client.injectKeyCode({
110 | action: AndroidKeyEventAction.Up,
111 | keyCode: this.key,
112 | repeat: 0,
113 | metaState: 0,
114 | });
115 | }
116 | }
117 |
118 |
119 | class ScpyClient {
120 | constructor() {
121 | this.deviceName = ''
122 | this.canvas = undefined
123 | this.parent = undefined
124 | this.device = undefined
125 | this.credentialStore = undefined
126 |
127 | if (typeof chrome !== "undefined" && chrome.runtime != undefined) {
128 | this.credentialStore = new AdbWebCredentialExtStore()
129 | } else {
130 | this.credentialStore = new AdbWebCredentialStore();
131 | }
132 |
133 | this.bitRatesCount = 0
134 | this.bitRatesTimerId = setInterval(() => {
135 | this.bitRatesCount = 0;
136 | }, 1000);
137 | this.decoder = undefined;
138 | this.logLevel = ScrcpyLogLevel.Debug;
139 | this.maxSize = 0
140 | this.bitRate = 8_000_000
141 | this.client = undefined
142 |
143 | this.homeKeyRepeater = undefined
144 | this.appSwitchKeyRepeater = undefined
145 | this.screenOn = undefined
146 |
147 | this.supported = AdbWebUsbBackend.isSupported();
148 | this.onVideoResize = (width, height) => { }
149 | }
150 |
151 | get connected() {
152 | // return true
153 | return this.client != undefined
154 | }
155 |
156 | createCanvas(parent) {
157 | if (this.decoder) {
158 | this.canvas = undefined;
159 | parent.removeChild(this.decoder.renderer)
160 | }
161 | this.parent = parent
162 |
163 | this.decoder = new WebCodecsDecoder();
164 | parent.appendChild(this.decoder.renderer);
165 | this.canvas = this.decoder.renderer;
166 | this.processEvents()
167 | }
168 |
169 | addlog(text) {
170 | let elm = document.querySelector("#logs");
171 | if (!elm) {
172 | return
173 | }
174 | let logElm = document.createElement("p");
175 | logElm.innerText = `${new Date().toLocaleString()} ${text}`;
176 | elm.appendChild(logElm);
177 | }
178 |
179 | async pickDevice() {
180 | let cacheDevices = (await AdbWebUsbBackend.getDevices()) || [];
181 | let deviceMeta = undefined;
182 | if (cacheDevices.length > 0) {
183 | deviceMeta = cacheDevices[0];
184 | } else {
185 | deviceMeta = await AdbWebUsbBackend.requestDevice();
186 | }
187 | return deviceMeta
188 | }
189 |
190 | async connectDevice(deviceMeta) {
191 | if (!this.device) {
192 | this.disconnect()
193 | }
194 |
195 | let streams = await deviceMeta.connect();
196 | this.addlog(`[client] authenticate ${deviceMeta.serial}`);
197 |
198 | this.device = await Adb.authenticate(
199 | streams,
200 | this.credentialStore,
201 | undefined
202 | );
203 |
204 | this.device.disconnected.then(() => {
205 | this.addlog("[client] disconnected done");
206 | });
207 |
208 | this.deviceName = deviceMeta.name
209 | return this.device;
210 | }
211 |
212 | async disconnect() {
213 | if (this.client) {
214 | this.client.close()
215 | this.client = undefined
216 | }
217 | if (this.device) {
218 | this.device.close()
219 | this.device = undefined
220 | }
221 |
222 | if (this.decoder && this.parent) {
223 | this.parent.removeChild(this.decoder.renderer)
224 | this.decoder.dispose()
225 | this.decoder = undefined
226 | this.parent = undefined
227 | this.canvas = undefined
228 | }
229 | }
230 |
231 | async connectScrcpy(parent) {
232 |
233 | await new WrapReadableStream({
234 | start(ctrl) {
235 | ctrl.enqueue(new Uint8Array(SCRCPY_SERVER_BIN));
236 | ctrl.close();
237 | },
238 | })
239 | .pipeThrough(new ChunkStream(ADB_SYNC_MAX_PACKET_SIZE))
240 | .pipeTo(pushServer(this.device));
241 |
242 |
243 | this.createCanvas(parent)
244 |
245 | const options = new ScrcpyOptions1_24({
246 | logLevel: this.logLevel,
247 | maxSize: this.maxSize,
248 | bitRate: this.bitRate,
249 | lockVideoOrientation: ScrcpyVideoOrientation.Unlocked,
250 | tunnelForward: false,
251 | sendDeviceMeta: false,
252 | sendDummyByte: false,
253 | codecOptions: new CodecOptions({
254 | profile: this.decoder.maxProfile,
255 | level: this.decoder.maxLevel,
256 | }),
257 | });
258 |
259 | this.addlog(`[client] Server arguments: ${options
260 | .formatServerArguments()
261 | .join(" ")}`);
262 |
263 | this.client = await ScrcpyClient.start(
264 | this.device,
265 | DEFAULT_SERVER_PATH,
266 | SCRCPY_SERVER_VERSION,
267 | options
268 | );
269 |
270 | this.client.stdout.pipeTo(
271 | new WritableStream({
272 | write: (line) => {
273 | this.addlog(`${line}`);
274 | },
275 | })
276 | );
277 |
278 | this.client.videoStream
279 | .pipeThrough(
280 | new InspectStream((packet) => {
281 | if (packet.type === "configuration") {
282 | const {
283 | croppedWidth,
284 | croppedHeight
285 | } = packet.data;
286 |
287 | this.width = croppedWidth
288 | this.height = croppedHeight
289 | this.onVideoResize(this.width, this.height);
290 |
291 | this.addlog(
292 | `[client] Video size changed: ${croppedWidth}x${croppedHeight}`
293 | );
294 | } else {
295 | this.bitRatesCount += packet.data.length;
296 | }
297 | })
298 | )
299 | .pipeTo(this.decoder.writable);
300 |
301 | this.homeKeyRepeater = new KeyRepeater(AndroidKeyCode.Home, this.client);
302 | this.appSwitchKeyRepeater = new KeyRepeater(AndroidKeyCode.AppSwitch, this.client);
303 | this.screenOn = undefined
304 | await this.toggleScreen()
305 | }
306 |
307 | calculatePointerPosition(clientX, clientY) {
308 | const view = this.canvas.getBoundingClientRect()
309 | const pointerViewX = clientX - view.x
310 | const pointerViewY = clientY - view.y
311 | const pointerScreenX = clamp(pointerViewX / view.width, 0, 1) * this.width
312 | const pointerScreenY = clamp(pointerViewY / view.height, 0, 1) * this.height
313 |
314 | return {
315 | x: pointerScreenX,
316 | y: pointerScreenY,
317 | };
318 | }
319 |
320 | async injectTouch(action, e) {
321 | if (!this.client) {
322 | return;
323 | }
324 |
325 | const {
326 | x,
327 | y
328 | } = this.calculatePointerPosition(e.clientX, e.clientY);
329 |
330 | await this.client.injectTouch({
331 | action,
332 | pointerId: e.pointerType === "mouse" ? BigInt(-1) : BigInt(e.pointerId),
333 | pointerX: x,
334 | pointerY: y,
335 | pressure: e.pressure * 65535,
336 | buttons: e.buttons,
337 | });
338 | };
339 |
340 | async handlePointerDown(e) {
341 | if (!this.client) {
342 | return
343 | }
344 | this.parent.focus();
345 | e.preventDefault();
346 | e.currentTarget.setPointerCapture(e.pointerId);
347 | await this.injectTouch(AndroidMotionEventAction.Down, e);
348 | }
349 |
350 | async handlePointerUp(e) {
351 | if (!this.client) {
352 | return
353 | }
354 | await this.injectTouch(AndroidMotionEventAction.Up, e);
355 | }
356 |
357 | async handlePointerMove(e) {
358 | await this.injectTouch(
359 | e.buttons === 0 ? AndroidMotionEventAction.HoverMove : AndroidMotionEventAction.Move,
360 | e
361 | );
362 | }
363 |
364 | async handleWheel(e) {
365 | if (!this.client) {
366 | return
367 | }
368 | e.preventDefault();
369 | e.stopPropagation();
370 |
371 | const {
372 | x,
373 | y
374 | } = this.calculatePointerPosition(e.clientX, e.clientY);
375 | this.client.injectScroll({
376 | pointerX: x,
377 | pointerY: y,
378 | scrollX: -Math.sign(e.deltaX),
379 | scrollY: -Math.sign(e.deltaY),
380 | buttons: 0,
381 | });
382 | }
383 |
384 | async handleBackPointerDown(e) {
385 | if (e.button !== 0 || !this.client) {
386 | return;
387 | }
388 | e.currentTarget.setPointerCapture(e.pointerId);
389 | await this.client.pressBackOrTurnOnScreen(AndroidKeyEventAction.Down);
390 | }
391 |
392 | async handleBackPointerUp(e) {
393 | if (e.button !== 0 || !this.client) {
394 | return;
395 | }
396 | await this.client.pressBackOrTurnOnScreen(AndroidKeyEventAction.Up);
397 | }
398 |
399 | async handleHomePointerDown(e) {
400 | if (e.button !== 0 || !this.client) {
401 | return;
402 | }
403 | e.currentTarget.setPointerCapture(e.pointerId);
404 | await this.homeKeyRepeater.press();
405 | }
406 |
407 | async handleHomePointerUp(e) {
408 | if (e.button !== 0 || !this.client) {
409 | return;
410 | }
411 | await this.homeKeyRepeater.release();
412 | }
413 |
414 | async handleAppSwitchPointerDown(e) {
415 | if (e.button !== 0 || !this.client) {
416 | return;
417 | }
418 | e.currentTarget.setPointerCapture(e.pointerId);
419 | await this.appSwitchKeyRepeater.press();
420 | }
421 |
422 | async handleAppSwitchPointerUp(e) {
423 | if (e.button !== 0 || !this.client) {
424 | return;
425 | }
426 | await this.appSwitchKeyRepeater.release();
427 | }
428 |
429 | async toggleScreen() {
430 | if (!this.client) {
431 | return true;
432 | }
433 |
434 | let mode = SCREEN_POWER_MODE_OFF
435 | if (!this.screenOn) {
436 | mode = SCREEN_POWER_MODE_NORMAL
437 | this.screenOn = true
438 | } else {
439 | this.screenOn = false
440 | }
441 |
442 | const controlStream = this.client.checkControlStream('setScreenPowerMode');
443 | const buffer = ScrcpySetPowerModeControlMessage.serialize({
444 | type: SetScreenPowerMode,
445 | mode,
446 | });
447 |
448 | if (buffer) {
449 | await controlStream.write(buffer);
450 | }
451 | return this.screenOn
452 | }
453 |
454 | getCanvas() {
455 | if (!this.client) {
456 | return
457 | }
458 | return this.canvas
459 | }
460 |
461 | getFileManager() {
462 | if (!this.client) {
463 | return
464 | }
465 | return new FileManager(this.device)
466 | }
467 |
468 | async handleKey(e) {
469 | if (!this.client) {
470 | return
471 | }
472 |
473 | e.preventDefault();
474 |
475 | const {
476 | key
477 | } = e;
478 | switch (key) {
479 | case "Escape":
480 | await this.client.pressBackOrTurnOnScreen(AndroidKeyEventAction.Down);
481 | await this.client.pressBackOrTurnOnScreen(AndroidKeyEventAction.Up);
482 | return
483 | case "Enter":
484 | case "Shift":
485 | case "Control":
486 | case "Alt":
487 | return
488 | }
489 |
490 | const keyCode = {
491 | Backspace: AndroidKeyCode.Delete,
492 | Space: AndroidKeyCode.Space,
493 | }[key]
494 |
495 | if (keyCode) {
496 | await this.client.injectKeyCode({
497 | action: AndroidKeyEventAction.Down,
498 | keyCode,
499 | metaState: 0,
500 | repeat: 0,
501 | });
502 | await this.client.injectKeyCode({
503 | action: AndroidKeyEventAction.Up,
504 | keyCode,
505 | metaState: 0,
506 | repeat: 0,
507 | });
508 | } else {
509 | this.client.injectText(key);
510 | }
511 | }
512 |
513 | processEvents() {
514 | this.emitKey = (evt) => {
515 | this.handleKey(evt)
516 | }
517 | this.bindKeyEvents = () => {
518 | this.canvas.focus();
519 | document.body.addEventListener('keydown', this.emitKey, true);
520 | }
521 |
522 | this.unbindKeyEvents = () => {
523 | document.body.removeEventListener('keydown', this.emitKey, true);
524 | }
525 |
526 | this.canvas.addEventListener('pointerdown', e => {
527 | return this.handlePointerDown(e)
528 | }, false);
529 | this.canvas.addEventListener('pointerup', e => {
530 | return this.handlePointerUp(e)
531 | }, false);
532 | this.canvas.addEventListener('pointermove', e => {
533 | return this.handlePointerMove(e)
534 | }, false);
535 | this.canvas.addEventListener('contextmenu', e => {
536 | e.preventDefault();
537 | }, false);
538 | this.canvas.addEventListener('wheel', e => {
539 | return this.handleWheel(e);
540 | }, false);
541 |
542 | this.canvas.addEventListener('mouseenter', e => {
543 | this.bindKeyEvents()
544 | }, false);
545 | this.canvas.addEventListener('mouseleave', e => {
546 | this.unbindKeyEvents()
547 | }, false);
548 | }
549 |
550 |
551 | }
552 | const client = new ScpyClient()
553 | export default client
--------------------------------------------------------------------------------
/src/docs/enabledebug.md:
--------------------------------------------------------------------------------
1 | Need to __turn on USB debugging mode__ on your phone and link the __USB cable__ to your computer
2 |
3 | Step 1: Tap Menu button so that you can enter into App drawer.
4 |
5 | Step 2: Access __Settings__ and tap __About phone__ which you can find at the bottom of setting interface.
6 |
7 | Step 3: In the __About phone__ interface, you will see __Build Number__ option at its bottom. Tap it continuously __until__ you see the countdown “You are now a developer”
8 |
9 | Step 4: Click the Back button and you can see the Developer options menu is activated.
10 |
11 | Step 5: Now you can turn __USB debugging__ on and then tap __OK__ to allow USB debugging.
12 |
13 | Step 6: Now, plug in your Android device using the USB cable.
14 |
15 | Step 7: __Allow USB Debugging confirmation on your phone.__
16 |
17 | Step 8: Select Your phone on Edge/Chrome confrim window, press __CONNECT__ to start.
--------------------------------------------------------------------------------
/src/docs/privacy.md:
--------------------------------------------------------------------------------
1 | ### Privacy Policy
2 |
3 | Your privacy is important to us. It is ForGrowth Inc.'s policy to respect your privacy regarding any information we may collect from you across our website, For Growth, and other sites we own and operate.
4 |
5 | #### Information we collect
6 |
7 | ##### Log data
8 | When you visit our website, our servers may automatically log the standard data provided by your web browser. It may include your computer’s Internet Protocol (IP) address, your browser type and version, the pages you visit, the time and date of your visit, the time spent on each page, and other details.
9 |
10 | ##### Device data
11 | We may also collect data about the device you’re using to access our website. This data may include the device type, operating system, unique device identifiers, device settings, and geo-location data. What we collect can depend on the individual settings of your device and software. We recommend checking the policies of your device manufacturer or software provider to learn what information they make available to us.
12 |
13 | ##### Personal information
14 | We may ask for personal information, such as your:
15 | ```shell
16 | Name
17 | Email
18 | Phone/mobile number
19 | Home/Mailing address
20 | Work address
21 | Website address
22 | Payment information
23 | ```
24 |
25 | ##### Business data
26 | Business data refers to data that accumulates over the normal course of operation on our platform. This may include transaction records, stored files, user profiles, analytics data and other metrics, as well as other types of information, created or generated, as users interact with our services.
27 |
28 | #### Legal bases for processing
29 |
30 | We will process your personal information lawfully, fairly and in a transparent manner. We collect and process information about you only where we have legal bases for doing so.
31 |
32 | These legal bases depend on the services you use and how you use them, meaning we collect and use your information only where:
33 |
34 | it’s necessary for the performance of a contract to which you are a party or to take steps at your request before entering into such a contract (for example, when we provide a service you request from us);
35 |
36 | it satisfies a legitimate interest (which is not overridden by your data protection interests), such as for research and development, to market and promote our services, and to protect our legal rights and interests;
37 |
38 | you give us consent to do so for a specific purpose; or we need to process your data to comply with a legal obligation.
39 |
40 | Where you consent to our use of information about you for a specific purpose, you have the right to change your mind at any time (but this will not affect any processing that has already taken place).
41 |
42 | We don’t keep personal information for longer than is necessary. While we retain this information, we will protect it within commercially acceptable means to prevent loss and theft, as well as unauthorized access, disclosure, copying, use or modification. That said, we advise that no method of electronic transmission or storage is 100% secure and cannot guarantee absolute data security. If necessary, we may retain your personal information for our compliance with a legal obligation or in order to protect your vital interests or the vital interests of another natural person.
43 |
44 | #### Collection and use of information
45 |
46 | We may collect, hold, use and disclose information for the following purposes and personal information will not be further processed in a manner that is incompatible with these purposes:
47 |
48 | - to provide you with our platform's core features;
49 | - to process any transactional or ongoing payments;
50 | - to enable you to access and use our website, associated applications and associated social media platforms;
51 | - to contact and communicate with you;
52 | - for internal record keeping and administrative purposes;
53 | - for analytics, market research and business development, including to operate and improve our website, associated applications and associated social media platforms;
54 | for advertising and marketing, including to send you promotional information about our products and services and information about third parties that we consider may be of interest to you;
55 | - to comply with our legal obligations and resolve any disputes that we may have; and
56 | - to consider your employment application.
57 |
58 | #### Disclosure of personal information to third parties
59 |
60 | We may disclose personal information to:
61 |
62 | - third party service providers for the purpose of enabling them to provide their services, including (without limitation) IT service providers, data storage, hosting and server providers, ad networks, analytics, error loggers, debt collectors, maintenance or problem-solving providers, marketing or advertising providers, professional advisors and payment systems operators;
63 | - our employees;
64 | - credit reporting agencies, courts, tribunals and regulatory authorities, in the event you fail to pay for goods or services we have provided to you;
65 | - courts, tribunals, regulatory authorities and law enforcement officers, as required by law, in connection with any actual or prospective legal proceedings, or in order to establish, - exercise or defend our legal rights;
66 | - third parties to collect and process data.
67 |
68 | #### International transfers of personal information
69 |
70 | The personal information we collect is stored and processed where we , providers maintain facilities. By providing us with your personal information, you consent to the disclosure to these third parties.
71 |
72 | We will ensure that any transfer of personal information from countries in the European Economic Area (EEA) to countries outside the EEA will be protected by appropriate safeguards, for example by using standard data protection clauses approved by the European Commission, or the use of binding corporate rules or other legally accepted means.
73 |
74 | Where we transfer personal information from a non-EEA country to another country, you acknowledge that third parties in other jurisdictions may not be subject to similar data protection laws to the ones in our jurisdiction. There are risks if any such third party engages in any act or practice that would contravene the data privacy laws in our jurisdiction and this might mean that you will not be able to seek redress under our jurisdiction’s privacy laws.
75 |
76 | #### Your rights and controlling your personal information
77 |
78 | ##### Choice and consent
79 | By providing personal information to us, you consent to us collecting, holding, using and disclosing your personal information in accordance with this privacy policy. If you are under 16 years of age, you must have, and warrant to the extent permitted by law to us, that you have your parent or legal guardian’s permission to access and use the website and they (your parents or guardian) have consented to you providing us with your personal information. You do not have to provide personal information to us, however, if you do not, it may affect your use of this website or the products and/or services offered on or through it.
80 |
81 | ##### Information from third parties
82 | If we receive personal information about you from a third party, we will protect it as set out in this privacy policy. If you are a third party providing personal information about somebody else, you represent and warrant that you have such person’s consent to provide the personal information to us.
83 |
84 | ##### Restrict
85 | You may choose to restrict the collection or use of your personal information. If you have previously agreed to us using your personal information for direct marketing purposes, you may change your mind at any time by contacting us using the details below. If you ask us to restrict or limit how we process your personal information, we will let you know how the restriction affects your use of our website or products and services.
86 |
87 | ##### Access and data portability
88 | You may request details of the personal information that we hold about you. You may request a copy of the personal information we hold about you. Where possible, we will provide this information in CSV format or other easily readable machine format. You may request that we erase the personal information we hold about you at any time. You may also request that we transfer this personal information to another third party.
89 |
90 | ##### Correction
91 | If you believe that any information we hold about you is inaccurate, out of date, incomplete, irrelevant or misleading, please contact us using the details below. We will take reasonable steps to correct any information found to be inaccurate, incomplete, misleading or out of date.
92 |
93 | ##### Notification of data breaches
94 | We will comply laws applicable to us in respect of any data breach.
95 |
96 | ##### Complaints
97 | If you believe that we have breached a relevant data protection law and wish to make a complaint, please contact us using the details below and provide us with full details of the alleged breach. We will promptly investigate your complaint and respond to you, in writing, setting out the outcome of our investigation and the steps we will take to deal with your complaint. You also have the right to contact a regulatory body or data protection authority in relation to your complaint.
98 |
99 | ##### Unsubscribe
100 | To unsubscribe from our e-mail database or opt-out of communications (including marketing communications), please contact us using the details below or opt-out using the opt-out facilities provided in the communication.
101 |
102 | #### Cookies
103 |
104 | We use “cookies” to collect information about you and your activity across our site. A cookie is a small piece of data that our website stores on your computer, and accesses each time you visit, so we can understand how you use our site. This helps us serve you content based on preferences you have specified. Please refer to our Cookie Policy for more information.
105 |
106 | #### Business transfers
107 |
108 | If we or our assets are acquired, or in the unlikely event that we go out of business or enter bankruptcy, we would include data among the assets transferred to any parties who acquire us. You acknowledge that such transfers may occur, and that any parties who acquire us may continue to use your personal information according to this policy.
109 |
110 | #### Changes to this policy
111 |
112 | At our discretion, we may change our privacy policy to reflect current acceptable practices. We will take reasonable steps to let users know about changes via our website. Your continued use of this site after any changes to this policy will be regarded as acceptance of our practices around privacy and personal information.
113 |
114 | If we make a significant change to this privacy policy, for example changing a lawful basis on which we process your personal information, we will ask you to re-consent to the amended privacy policy.
115 |
116 | z-yun@fourz.cn
117 |
118 | This policy is effective as of 30 April 2022.
--------------------------------------------------------------------------------
/src/docs/terms.md:
--------------------------------------------------------------------------------
1 | ### Terms and Conditions
2 |
3 | Please read these Terms and Conditions ("Terms", "Terms and Conditions") carefully before using the https://browserlify.com website and all operated by ForGrowth.
4 |
5 | The following terminology applies to these Terms and Conditions, Privacy Statement and Disclaimer Notice and any or all Agreements:
6 |
7 | “You” and “Your” refers to the user accessing this website and accepting the Company’s terms and conditions.
8 |
9 | “Ourselves”, “We”, “Our” and "Us", refers to ForGrowth.
10 |
11 | #### Acceptance of ForGrowth Terms and Conditions of Service
12 |
13 | * By visiting our site and/ or purchasing something from us, you engage in our “Service”.
14 | Your access to and use of the Service is conditioned on your acceptance of and compliance with these Terms.
15 | * These Terms apply to all visitors, users and others who access or use the Service.
16 | * By accessing or using the Service you agree to be bound by these Terms. If you disagree with any part of the terms then you may not access the Service.
17 |
18 | #### Credit Card Details
19 |
20 | ForGrowth will not ask for your credit card details, except for when subscribing to the ForGrowth recurring payment subscription service.
21 | We advise our customers to not enter their credit cards details in the Service or by submitting such details in any other form if you are not setting up your ForGrowth recurring payment subscription service.
22 | If you are unsure we urge you to contact us at hello@browserlify.com . ForGrowth can not be held responsible for any fraud or phishing attempts
23 |
24 | #### Change of Use
25 |
26 | ForGrowth reserves the right, at our sole discretion, to:
27 | * change, modify or remove (temporarily or permanently) the Service or any part of it without notice and you confirm that the Service shall not be liable to you for any such change or removal.
28 | * change these Terms and Conditions at any time.
29 | * change the price for the Service without notice and your continued use of the Service following any changes shall be deemed to be your acceptance of such change.
30 |
31 | #### Links to, and use of, Third Party
32 |
33 | * ForGrowth may include links to, or make use of, third party software and services that are controlled and maintained by others (hereby referred to only as Third Party).
34 | * Any link to or usage of Third Party is not an endorsement of Third Party.
35 | * You acknowledge and agree that we are not responsible for the content or availability of any such Third Party.
36 |
37 | #### Copyright
38 |
39 | * All copyright, trade-marks and all other intellectual property rights of ForGrowth and the Service (including without limitation the design, text, graphics and all software and source codes connected with ForGrowth are owned by or licensed to ForGrowth or otherwise used by ForGrowth as permitted by law.)
40 | * In accessing ForGrowth you agree that you will access the Service solely for your personal, non-commercial use.[b] None of the content may be downloaded, copied, reproduced, transmitted, stored, sold or distributed without the prior written consent of the copyright holder.
41 |
42 | #### Disclaimers and Liability
43 |
44 | * Use of the Service is at your own risk. Our Service is provided on an AS IS and AS AVAILABLE basis without any representation or endorsement made and without warranty of any kind whether express or implied, including but not limited to the implied warranties of satisfactory quality, fitness for a particular purpose, non-infringement, compatibility, security and accuracy.
45 | * To the extent permitted by law, ForGrowth will not be liable for any indirect or consequential loss or damage whatever (including without limitation loss of business, opportunity, data, profits) arising out of or in connection with the use of the Service.
46 | * ForGrowth makes no warranty that the functionality of the Service will be uninterrupted or error free, that defects will be corrected or that the Service or any server that makes it available are free of viruses or anything else which may be harmful or destructive.
47 |
48 | #### Termination of Service
49 |
50 | * The obligations and liabilities of the parties incurred prior to the termination date shall survive the termination of this agreement for all purposes.
51 | * These Terms of Service are effective unless and until terminated by either you or us. You may terminate these Terms of Service at any time by notifying us that you no longer wish to use our Services, or when you cease using our site.
52 | * If in our sole judgment you fail, or we suspect that you have failed, to comply with any term or provision of these Terms of Service, we also may terminate this agreement at any time without notice and you will remain liable for all amounts due up to and including the date of termination; and/or accordingly may deny you access to our Services (or any part thereof).
53 |
54 | #### Indemnity
55 |
56 | You agree to indemnify and hold ForGrowth and its employees and agents harmless from and against all liabilities, legal fees, damages, losses, costs and other expenses in relation to any claims or actions brought against ForGrowth arising out of any breach by you of these
57 | Terms and Conditions or other liabilities arising out of your use of the Service.
58 |
59 | #### Severability
60 |
61 | If the event that any of these Terms and Conditions should be determined to be invalid, illegal or unenforceable for any reason by any court of competent jurisdiction then such Term or Condition shall be severed and the remaining Terms and Conditions shall survive and remain in full force and effect and continue to be binding and enforceable.
62 |
63 | #### Waiver
64 |
65 | If you breach these Conditions of Use and we take no action, we will still be entitled to use our rights and remedies in any other situation where you breach these Conditions of Use.
66 |
67 | #### Governing Law
68 |
69 | These Terms and Conditions shall be governed by and construed in accordance with the law of the United States of America and you hereby submit to the exclusive jurisdiction of the United States courts.
70 |
71 | #### Contact
72 |
73 | If you have any questions about these Terms, please contact us at z-yun@fourz.cn
--------------------------------------------------------------------------------
/src/extension.js:
--------------------------------------------------------------------------------
1 | import { createApp } from 'vue'
2 | import './style.css'
3 | import './assets/fonts/font.css'
4 | import Extension from './Extension.vue'
5 |
6 | createApp(Extension).mount('#app')
7 |
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from 'vue'
2 | import './style.css'
3 | import './assets/fonts/font.css'
4 | import Phone from './components/Phone.vue'
5 |
6 | createApp(Phone).mount('#app')
7 |
--------------------------------------------------------------------------------
/src/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Phone On Web | Android in browser",
3 | "description": "Using android phone in web browser without root, not need software download.",
4 | "homepage_url": "http://browserlify.com/?from=webstore_",
5 | "version": "-",
6 | "manifest_version": 3,
7 | "background": {
8 | "service_worker": "background.js"
9 | },
10 | "permissions": [
11 | "storage"
12 | ],
13 | "icons": {
14 | "64": "icons/icon_64.png",
15 | "96": "icons/icon_64.png",
16 | "128": "icons/icon_64.png"
17 | },
18 | "action": {
19 | "default_title": "Using android in browser"
20 | },
21 | "web_accessible_resources": [
22 | {
23 | "resources": [
24 | "icons/*",
25 | "assets/*"
26 | ],
27 | "matches": [
28 | ""
29 | ]
30 | }
31 | ]
32 | }
--------------------------------------------------------------------------------
/src/style.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
--------------------------------------------------------------------------------
/tailwind.config.cjs:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | const defaultTheme = require('tailwindcss/defaultTheme')
3 |
4 | module.exports = {
5 | content: [
6 | "./extension.html",
7 | "./src/**/*.{vue,js,ts,jsx,tsx}",
8 | ],
9 | theme: {
10 | extend: {
11 | fontFamily: {
12 | sans: ['Nunito', ...defaultTheme.fontFamily.sans],
13 | },
14 | },
15 | },
16 | plugins: [
17 | ],
18 | }
19 |
--------------------------------------------------------------------------------
/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import { resolve } from 'path'
3 | import vue from '@vitejs/plugin-vue'
4 | import Markdown from './plugins/md-loader'
5 | import Binary from './plugins/binary-loader'
6 |
7 | // https://vitejs.dev/config/
8 | export default defineConfig({
9 | define: {
10 | 'process.env': process.env
11 | },
12 | build: {
13 | lib: {
14 | entry: resolve(__dirname, 'src/main.js'),
15 | name: 'andbrowser',
16 | // the proper extensions will be added
17 | fileName: 'andbrowser'
18 | },
19 | },
20 | plugins: [
21 | vue(),
22 | Markdown(),
23 | Binary(),
24 | ],
25 | assetsInclude: ["**/*.md", "**/*.jar"],
26 | })
27 |
--------------------------------------------------------------------------------
/vite.extension.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import { resolve } from 'path'
3 | import vue from '@vitejs/plugin-vue'
4 | import Markdown from './plugins/md-loader'
5 | import Binary from './plugins/binary-loader'
6 | import { viteStaticCopy } from 'vite-plugin-static-copy'
7 | import { version } from './package.json'
8 | // https://vitejs.dev/config/
9 |
10 | export default defineConfig({
11 | define: {
12 | 'process.env': process.env
13 | },
14 | plugins: [
15 | vue(),
16 | Markdown(),
17 | Binary(),
18 | viteStaticCopy({
19 | targets: [
20 | { src: 'src/app/background.js', dest: '.' },
21 | { src: 'src/app/plausible.js', dest: '.' },
22 | { src: 'src/assets/icon_64.png', dest: 'icons' },
23 | {
24 | src: 'src/manifest.json',
25 | dest: '.',
26 | transform: (content, fname) => {
27 | var obj = JSON.parse(content)
28 | obj.version = version
29 | obj.homepage_url = obj.homepage_url + process.env.VITE_STORE
30 | return JSON.stringify(obj, null, 2)
31 | },
32 | }
33 | ]
34 | })
35 | ],
36 | assetsInclude: ["**/*.md", "**/*.jar"],
37 | build: {
38 | assetsInlineLimit: 100 * 1024,
39 | rollupOptions: {
40 | output: {
41 | entryFileNames: `assets/[name].js`,
42 | chunkFileNames: `assets/[name].js`,
43 | assetFileNames: `assets/[name].[ext]`
44 | }
45 | }
46 | }
47 | })
48 |
--------------------------------------------------------------------------------