From 41a20b50ea04aab0e1c7cbe9f9fab3c0c5888d4b Mon Sep 17 00:00:00 2001 From: rustdesk Date: Sat, 22 Jun 2024 12:29:20 +0800 Subject: [PATCH] split web js to v1 and v2 --- .github/workflows/flutter-build.yml | 1 + flutter/web/README.md | 17 - flutter/web/favicon.svg | 1 - flutter/web/icons/Icon-192.png | Bin 6339 -> 0 bytes flutter/web/icons/Icon-512.png | Bin 17282 -> 0 bytes flutter/web/icons/Icon-maskable-192.png | Bin 6339 -> 0 bytes flutter/web/icons/Icon-maskable-512.png | Bin 17282 -> 0 bytes flutter/web/js/src/consts.ts | 19 - flutter/web/js/src/globals.js | 792 ------------------ flutter/web/v1/README.md | 1 + flutter/web/{ => v1}/index.html | 0 flutter/web/{ => v1}/js/.gitattributes | 0 flutter/web/{ => v1}/js/.gitignore | 0 flutter/web/{ => v1}/js/.yarnrc.yml | 0 flutter/web/{ => v1}/js/gen_js_from_hbb.py | 0 flutter/web/{ => v1}/js/index.html | 0 flutter/web/{ => v1}/js/package.json | 6 +- flutter/web/{ => v1}/js/src/codec.js | 2 +- flutter/web/{ => v1}/js/src/common.ts | 0 flutter/web/{ => v1}/js/src/connection.ts | 368 +------- flutter/web/v1/js/src/globals.js | 383 +++++++++ flutter/web/{ => v1}/js/src/main.ts | 0 flutter/web/{ => v1}/js/src/style.css | 0 flutter/web/{ => v1}/js/src/ui.js | 0 flutter/web/{ => v1}/js/src/vite-env.d.ts | 0 flutter/web/{ => v1}/js/src/websock.ts | 0 flutter/web/{ => v1}/js/ts_proto.py | 0 flutter/web/{ => v1}/js/tsconfig.json | 0 flutter/web/{ => v1}/js/vite.config.js | 0 flutter/web/{ => v1}/js/yarn.lock | 0 .../web/{ => v1}/libs/firebase-analytics.js | 0 flutter/web/{ => v1}/libs/firebase-app.js | 0 flutter/web/{ => v1}/manifest.json | 0 flutter/web/{ => v1}/yarn.lock | 0 flutter/web/{ => v1}/yuv.js | 0 flutter/web/{ => v1}/yuv.wasm | Bin flutter/web/v2/README.md | 1 + 37 files changed, 435 insertions(+), 1156 deletions(-) delete mode 100644 flutter/web/README.md delete mode 100644 flutter/web/favicon.svg delete mode 100644 flutter/web/icons/Icon-192.png delete mode 100644 flutter/web/icons/Icon-512.png delete mode 100644 flutter/web/icons/Icon-maskable-192.png delete mode 100644 flutter/web/icons/Icon-maskable-512.png delete mode 100644 flutter/web/js/src/consts.ts delete mode 100644 flutter/web/js/src/globals.js create mode 100644 flutter/web/v1/README.md rename flutter/web/{ => v1}/index.html (100%) rename flutter/web/{ => v1}/js/.gitattributes (100%) rename flutter/web/{ => v1}/js/.gitignore (100%) rename flutter/web/{ => v1}/js/.yarnrc.yml (100%) rename flutter/web/{ => v1}/js/gen_js_from_hbb.py (100%) rename flutter/web/{ => v1}/js/index.html (100%) rename flutter/web/{ => v1}/js/package.json (70%) rename flutter/web/{ => v1}/js/src/codec.js (95%) rename flutter/web/{ => v1}/js/src/common.ts (100%) rename flutter/web/{ => v1}/js/src/connection.ts (64%) create mode 100644 flutter/web/v1/js/src/globals.js rename flutter/web/{ => v1}/js/src/main.ts (100%) rename flutter/web/{ => v1}/js/src/style.css (100%) rename flutter/web/{ => v1}/js/src/ui.js (100%) rename flutter/web/{ => v1}/js/src/vite-env.d.ts (100%) rename flutter/web/{ => v1}/js/src/websock.ts (100%) rename flutter/web/{ => v1}/js/ts_proto.py (100%) rename flutter/web/{ => v1}/js/tsconfig.json (100%) rename flutter/web/{ => v1}/js/vite.config.js (100%) rename flutter/web/{ => v1}/js/yarn.lock (100%) rename flutter/web/{ => v1}/libs/firebase-analytics.js (100%) rename flutter/web/{ => v1}/libs/firebase-app.js (100%) rename flutter/web/{ => v1}/manifest.json (100%) rename flutter/web/{ => v1}/yarn.lock (100%) rename flutter/web/{ => v1}/yuv.js (100%) rename flutter/web/{ => v1}/yuv.wasm (100%) create mode 100644 flutter/web/v2/README.md diff --git a/.github/workflows/flutter-build.yml b/.github/workflows/flutter-build.yml index c8865233f..70312d3e3 100644 --- a/.github/workflows/flutter-build.yml +++ b/.github/workflows/flutter-build.yml @@ -1573,6 +1573,7 @@ jobs: flatpak/rustdesk-${{ env.VERSION }}-${{ matrix.job.arch }}.flatpak build-rustdesk-web: + if: False name: build-rustdesk-web runs-on: ubuntu-20.04 strategy: diff --git a/flutter/web/README.md b/flutter/web/README.md deleted file mode 100644 index e605e72c0..000000000 --- a/flutter/web/README.md +++ /dev/null @@ -1,17 +0,0 @@ -# RustDesk web - -## Functions - -### Current and planned - -- [x] Outgoing -- [ ] Address book -- [ ] Force relay - -### Unsupported - -1. Incoming -2. LAN -3. Gpu texture render. We use WebGL instead. - -### No plans diff --git a/flutter/web/favicon.svg b/flutter/web/favicon.svg deleted file mode 100644 index 0234ca69e..000000000 --- a/flutter/web/favicon.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/flutter/web/icons/Icon-192.png b/flutter/web/icons/Icon-192.png deleted file mode 100644 index 2ff3bb45c06170ece6a60c3df422472c6d7b5a61..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6339 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 Lg<-`7xA^}9wKTWe diff --git a/flutter/web/icons/Icon-512.png b/flutter/web/icons/Icon-512.png deleted file mode 100644 index 4d94e3123b8c87a2fd8bdc1dafc28a819ae5353b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17282 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 Lg<-`7xA^}9wKTWe diff --git a/flutter/web/icons/Icon-maskable-512.png b/flutter/web/icons/Icon-maskable-512.png deleted file mode 100644 index 4d94e3123b8c87a2fd8bdc1dafc28a819ae5353b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17282 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 { - return /(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|ipad|iris|kindle|Android|Silk|lge |maemo|midp|mmp|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i.test(navigator.userAgent) - || /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(navigator.userAgent.substr(0, 4)); -} - -export function isDesktop() { - return !isMobile(); -} - -export function msgbox(type, title, text, link) { - if (!type || (type == 'error' && !text)) return; - const text2 = text.toLowerCase(); - var hasRetry = checkIfRetry(type, title, text) ? 'true' : ''; - onGlobalEvent(JSON.stringify({ name: 'msgbox', type, title, text, link: link ?? '', hasRetry })); -} - -function jsonfyForDart(payload) { - var tmp = {}; - for (const [key, value] of Object.entries(payload)) { - if (!key) continue; - if (value instanceof String || typeof value == 'string') { - tmp[key] = value; - } else if (value instanceof Uint8Array) { - tmp[key] = '[' + value.toString() + ']'; - } else { - tmp[key] = JSON.stringify(value); - } - } - return tmp; -} - -export function pushEvent(name, payload) { - payload = jsonfyForDart(payload); - payload.name = name; - onGlobalEvent(JSON.stringify(payload)); -} - -let yuvWorker; -let yuvCanvas; -let gl; -let pixels; -let flipPixels; -let oldSize; -if (YUVCanvas.WebGLFrameSink.isAvailable()) { - var canvas = document.createElement('canvas'); - yuvCanvas = YUVCanvas.attach(canvas, { webGL: true }); - gl = canvas.getContext("webgl"); -} else { - yuvWorker = new Worker("./yuv.js"); -} -let testSpeed = [0, 0]; - -export function draw(display, frame) { - if (yuvWorker) { - // frame's (y/u/v).bytes already detached, can not transferrable any more. - yuvWorker.postMessage({ display, frame }); - } else { - var tm0 = new Date().getTime(); - yuvCanvas.drawFrame(frame); - var width = canvas.width; - var height = canvas.height; - var size = width * height * 4; - if (size != oldSize) { - pixels = new Uint8Array(size); - flipPixels = new Uint8Array(size); - oldSize = size; - } - gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, pixels); - const row = width * 4; - const end = (height - 1) * row; - for (let i = 0; i < size; i += row) { - flipPixels.set(pixels.subarray(i, i + row), end - i); - } - onRgba(display, flipPixels); - testSpeed[1] += new Date().getTime() - tm0; - testSpeed[0] += 1; - if (testSpeed[0] > 30) { - console.log('gl: ' + parseInt('' + testSpeed[1] / testSpeed[0])); - testSpeed = [0, 0]; - } - } - /* - var testCanvas = document.getElementById("test-yuv-decoder-canvas"); - if (testCanvas && currentFrame) { - var ctx = testCanvas.getContext("2d"); - testCanvas.width = frame.format.displayWidth; - testCanvas.height = frame.format.displayHeight; - var img = ctx.createImageData(testCanvas.width, testCanvas.height); - img.data.set(currentFrame); - ctx.putImageData(img, 0, 0); - } - */ -} - -export function sendOffCanvas(c) { - let canvas = c.transferControlToOffscreen(); - yuvWorker.postMessage({ canvas }, [canvas]); -} - -export function setConn(conn) { - window.curConn = conn; -} - -export function getConn() { - return window.curConn; -} - -export async function startConn(id) { - setByName('remote_id', id); - await curConn.start(id); -} - -export function close() { - getConn()?.close(); - setConn(undefined); -} - -export function newConn() { - window.curConn?.close(); - const conn = new Connection(); - setConn(conn); - return conn; -} - -let sodium; -export async function verify(signed, pk) { - if (!sodium) { - await _sodium.ready; - sodium = _sodium; - } - if (typeof pk == 'string') { - pk = decodeBase64(pk); - } - return sodium.crypto_sign_open(signed, pk); -} - -export function decodeBase64(pk) { - return sodium.from_base64(pk, sodium.base64_variants.ORIGINAL); -} - -export function genBoxKeyPair() { - const pair = sodium.crypto_box_keypair(); - const sk = pair.privateKey; - const pk = pair.publicKey; - return [sk, pk]; -} - -export function genSecretKey() { - return sodium.crypto_secretbox_keygen(); -} - -export function seal(unsigned, theirPk, ourSk) { - const nonce = Uint8Array.from(Array(24).fill(0)); - return sodium.crypto_box_easy(unsigned, nonce, theirPk, ourSk); -} - -function makeOnce(value) { - var byteArray = Array(24).fill(0); - - for (var index = 0; index < byteArray.length && value > 0; index++) { - var byte = value & 0xff; - byteArray[index] = byte; - value = (value - byte) / 256; - } - - return Uint8Array.from(byteArray); -}; - -export function encrypt(unsigned, nonce, key) { - return sodium.crypto_secretbox_easy(unsigned, makeOnce(nonce), key); -} - -export function decrypt(signed, nonce, key) { - return sodium.crypto_secretbox_open_easy(signed, makeOnce(nonce), key); -} - -window.setByName = (name, value) => { - switch (name) { - case 'remote_id': - localStorage.setItem('remote-id', value); - break; - case 'connect': - newConn(); - startConn(value); - break; - case 'login': - value = JSON.parse(value); - curConn.setRemember(value.remember); - curConn.login({ - os_login: { - username: value.os_username, - password: value.os_password, - }, - password: value.password, - }); - break; - case 'close': - close(); - break; - case 'refresh': - curConn.refresh(); - break; - case 'reconnect': - curConn?.reconnect(); - break; - case 'toggle_option': - curConn.toggleOption(value); - break; - case 'toggle_privacy_mode': - curConn.togglePrivacyMode(value); - break; - case 'image_quality': - curConn.setImageQuality(value); - break; - case 'lock_screen': - curConn.lockScreen(); - break; - case 'ctrl_alt_del': - curConn.ctrlAltDel(); - break; - case 'switch_display': - curConn.switchDisplay(value); - break; - case 'remove': - const peers = getPeers(); - delete peers[value]; - localStorage.setItem('peers', JSON.stringify(peers)); - break; - case 'input_key': - value = JSON.parse(value); - curConn.inputKey(value.name, value.down == 'true', value.press == 'true', value.alt == 'true', value.ctrl == 'true', value.shift == 'true', value.command == 'true'); - break; - case 'input_string': - curConn.inputString(value); - break; - case 'send_mouse': - if (!curConn) return; - let mask = 0; - value = JSON.parse(value); - switch (value.type) { - case 'down': - mask = 1; - break; - case 'up': - mask = 2; - break; - case 'wheel': - mask = 3; - break; - } - switch (value.buttons) { - case 'left': - mask |= 1 << 3; - break; - case 'right': - mask |= 2 << 3; - break; - case 'wheel': - mask |= 4 << 3; - } - curConn.inputMouse(mask, parseInt(value.x || '0'), parseInt(value.y || '0'), value.alt == 'true', value.ctrl == 'true', value.shift == 'true', value.command == 'true'); - break; - case 'send_2fa': - curConn.send2fa(value); - break; - case 'option': - value = JSON.parse(value); - localStorage.setItem(value.name, value.value); - break; - case 'options': - value = JSON.parse(value); - for (const [key, value] of Object.entries(value)) { - localStorage.setItem(key, value); - } - break; - case 'option:local': - case 'option:flutter:local': - case 'option:flutter:peer': - value = JSON.parse(value); - localStorage.setItem(name + ':' + value.name, value.value); - break; - case 'option:user:default': - setUserDefaultOption(value); - break; - case 'option:session': - value = JSON.parse(value); - curConn.setOption(value.name, value.value); - break; - case 'option:peer': - setPeerOption(value); - break; - case 'option:toggle': - return curConn.toggleOption(value); - case 'input_os_password': - curConn.inputOsPassword(value); - break; - case 'session_add_sync': - return sessionAdd(value); - case 'session_start': - sessionStart(value); - break; - case 'session_close': - sessionClose(value); - break; - case 'elevate_with_logon': - curConn.elevateWithLogon(value); - break; - case 'forget': - curConn.setRemember(false); - break; - case 'peer_has_password': - const options = getPeers()[value] || {}; - return (options['password'] ?? '') !== ''; - case 'peer_exists': - return !(!getPeers()[value]); - case 'restart': - curConn.restart(); - break; - case 'fav': - return localStorage.setItem('fav', value); - case 'query_onlines': - queryOnlines(value); - break; - case 'change_prefer_codec': - curConn.changePreferCodec(value); - case 'cursor': - setCustomCursor(value); - break; - default: - break; - } -} - -window.getByName = (name, arg) => { - let v = _getByName(name, arg); - if (typeof v == 'string' || v instanceof String) return v; - if (v == undefined || v == null) return ''; - return JSON.stringify(v); -} - -function _getByName(name, arg) { - switch (name) { - case 'remote_id': - return localStorage.getItem('remote-id'); - case 'remember': - return curConn.getRemember(); - case 'toggle_option': - return curConn.getOption(arg) || false; - case 'option': - return localStorage.getItem(arg); - case 'options': - const keys = [ - 'custom-rendezvous-server', - 'relay-server', - 'api-server', - 'key' - ]; - const obj = {}; - keys.forEach(key => { - const v = localStorage.getItem(key); - if (v) obj[key] = v; - }); - return JSON.stringify(obj); - case 'option:local': - case 'option:flutter:local': - case 'option:flutter:peer': - return localStorage.getItem(name + ':' + arg); - case 'image_quality': - return curConn.getImageQuality(); - case 'translate': - arg = JSON.parse(arg); - return translate(arg.locale, arg.text); - case 'option:user:default': - return getUserDefaultOption(arg); - case 'option:session': - if (curConn) { - return curConn.getOption(arg); - } else { - return getUserDefaultOption(arg); - } - case 'option:peer': - return getPeerOption(arg); - case 'option:toggle': - return curConn.getToggleOption(arg); - case 'get_conn_status': - if (curConn) { - return curConn.getStatus(); - } else { - return JSON.stringify({ status_num: 0 }); - } - case 'test_if_valid_server': - break; - case 'version': - return version; - case 'load_recent_peers': - loadRecentPeers(); - break; - case 'load_fav_peers': - loadFavPeers(); - break; - case 'fav': - return localStorage.getItem('fav') ?? '[]'; - case 'load_recent_peers_sync': - return JSON.stringify({ - peers: JSON.stringify(getRecentPeers()) - }); - case 'api_server': - return getApiServer(); - case 'is_using_public_server': - return !localStorage.getItem('custom-rendezvous-server'); - case 'get_version_number': - return getVersionNumber(arg); - case 'audit_server': - return getAuditServer(arg); - case 'alternative_codecs': - return getAlternativeCodecs(); - case 'screen_info': - return JSON.stringify({ - frame: { - l: window.screenX, - t: window.screenY, - r: window.screenX + window.innerWidth, - b: window.screenY + window.innerHeight, - }, - visibleFrame: { - l: window.screen.availLeft, - t: window.screen.availTop, - r: window.screen.availLeft + window.screen.availWidth, - b: window.screen.availTop + window.screen.availHeight, - }, - scaleFactor: window.devicePixelRatio, - }); - case 'main_display': - return JSON.stringify({ - w: window.screen.availWidth, - h: window.screen.availHeight, - scaleFactor: window.devicePixelRatio, - }); - } - return ''; -} - -let opusWorker = new Worker("./libopus.js"); -let pcmPlayer; - -export function initAudio(channels, sampleRate) { - pcmPlayer = newAudioPlayer(channels, sampleRate); - opusWorker.postMessage({ channels, sampleRate }); -} - -export function playAudio(packet) { - opusWorker.postMessage(packet, [packet.buffer]); -} - -window.init = async () => { - if (yuvWorker) { - yuvWorker.onmessage = (e) => { - onRgba(e.data.display, e.data.frame); - } - } - opusWorker.onmessage = (e) => { - pcmPlayer.feed(e.data); - } - loadVp9(() => { }); - await initZstd(); - console.log('init done'); -} - -export function getPeers() { - return getJsonObj('peers'); -} - -export function getJsonObj(key) { - try { - return JSON.parse(localStorage.getItem(key)) || {}; - } catch (e) { - return {}; - } -} - -function newAudioPlayer(channels, sampleRate) { - return new PCMPlayer({ - channels, - sampleRate, - flushingTime: 2000 - }); -} - -export function copyToClipboard(text) { - if (window.clipboardData && window.clipboardData.setData) { - // Internet Explorer-specific code path to prevent textarea being shown while dialog is visible. - return window.clipboardData.setData("Text", text); - } - else if (document.queryCommandSupported && document.queryCommandSupported("copy")) { - var textarea = document.createElement("textarea"); - textarea.textContent = text; - textarea.style.position = "fixed"; // Prevent scrolling to bottom of page in Microsoft Edge. - document.body.appendChild(textarea); - textarea.select(); - try { - return document.execCommand("copy"); // Security exception may be thrown by some browsers. - } - catch (ex) { - console.warn("Copy to clipboard failed.", ex); - // return prompt("Copy to clipboard: Ctrl+C, Enter", text); - } - finally { - document.body.removeChild(textarea); - } - } -} - -// Dup to the function in hbb_common, lib.rs -// Maybe we need to move this function to js part. -export function getVersionNumber(v) { - try { - let versions = v.split('-'); - - let n = 0; - - // The first part is the version number. - // 1.1.10 -> 1001100, 1.2.3 -> 1001030, multiple the last number by 10 - // to leave space for patch version. - if (versions.length > 0) { - let last = 0; - for (let x of versions[0].split('.')) { - last = parseInt(x) || 0; - n = n * 1000 + last; - } - n -= last; - n += last * 10; - } - - if (versions.length > 1) { - n += parseInt(versions[1]) || 0; - } - - // Ignore the rest - - return n; - } - catch (e) { - console.error('Failed to parse version number: "' + v + '" ' + e.message); - return 0; - } -} - -// Set the cursor for the flutter-view element -function setCustomCursor(value) { - try { - const obj = JSON.parse(value); - // document querySelector or evaluate can not find the custom element - var body = document.body; - for (var i = 0; i < body.children.length; i++) { - var child = body.children[i]; - if (child.tagName == 'FLUTTER-VIEW') { - child.style.cursor = `url(${obj.url}) ${obj.hotx} ${obj.hoty}, auto`; - } - } - } catch (e) { - console.error('Failed to set custom cursor: ' + e.message); - } -} - -// ========================== options begin ========================== -function setUserDefaultOption(value) { - try { - const ojb = JSON.parse(value); - const userDefaultOptions = JSON.parse(localStorage.getItem('user-default-options')) || {}; - userDefaultOptions[ojb.name] = ojb.value; - localStorage.setItem('user-default-options', JSON.stringify(userDefaultOptions)); - } - catch (e) { - console.error('Failed to set user default options: ' + e.message); - } -} - -export function getUserDefaultOption(value) { - const defaultOptions = { - 'view_style': 'original', - 'scroll_style': 'scrollauto', - 'image_quality': 'balanced', - 'codec-preference': 'auto', - 'custom_image_quality': '50', - 'custom-fps': '30', - }; - try { - const userDefaultOptions = JSON.parse(localStorage.getItem('user-default-options')) || {}; - return userDefaultOptions[value] || defaultOptions[value] || ''; - } - catch (e) { - console.error('Failed to get user default options: ' + e.message); - return defaultOptions[value] || ''; - } -} - -function getPeerOption(value) { - try { - const obj = JSON.parse(value); - const options = getPeers()[obj.id] || {}; - return options[obj.name] ?? getUserDefaultOption(obj.name); - } - catch (e) { - console.error('Failed to get peer option: "' + value + '", ' + e.message); - } -} - -function setPeerOption(param) { - try { - const obj = JSON.parse(param); - const id = obj.id; - const name = obj.name; - const value = obj.value; - const peers = getPeers(); - const options = peers[id] || {}; - - if (value == undefined) { - delete options[name]; - } else { - options[name] = value; - } - options["tm"] = new Date().getTime(); - peers[id] = options; - localStorage.setItem("peers", JSON.stringify(peers)); - } - catch (e) { - console.error('Failed to set peer option: "' + value + '", ' + e.message); - } -} -// ========================= options end =========================== - -// ========================== peers begin ========================== -function getRecentPeers() { - const peers = []; - for (const [id, value] of Object.entries(getPeers())) { - if (!id) continue; - const tm = value['tm']; - const info = value['info']; - const cardInfo = { - id: id, - username: info['username'] || '', - hostname: info['hostname'] || '', - platform: info['platform'] || '', - alias: value.alias || '', - }; - if (!tm || !cardInfo) continue; - peers.push([tm, id, cardInfo]); - } - return peers.sort().reverse().map(x => x[2]); -} - -function loadRecentPeers() { - const peersRecent = getRecentPeers(); - if (peersRecent) { - onRegisteredEvent(JSON.stringify({ name: 'load_recent_peers', peers: JSON.stringify(peersRecent) })); - } -} - -function loadFavPeers() { - try { - const fav = localStorage.getItem('fav') ?? '[]'; - const favs = JSON.parse(fav); - const peersFav = getRecentPeers().filter(x => favs.includes(x.id)); - if (peersFav) { - onRegisteredEvent(JSON.stringify({ name: 'load_fav_peers', peers: JSON.stringify(peersFav) })); - } - } catch (e) { - console.error('Failed to load fav peers: ' + e.message); - } -} - -export function queryOnlines(value) { - // TODO: implement this -} -// ========================== peers end =========================== - -// ========================== session begin ========================== -function sessionAdd(value) { - try { - const data = JSON.parse(value); - window.curConn?.close(); - const conn = new Connection(); - setConn(conn); - return ''; - } catch (e) { - return e.message; - } -} - -function sessionStart(value) { - try { - const conn = getConn(); - if (!conn) { - return; - } - - const data = JSON.parse(value); - if (data['id']) { - startConn(data['id']); - } else { - msgbox('error', 'Error', 'No id found in session data ' + value, ''); - } - } catch (e) { - // TODO: better error handling - msgbox('error', 'Error', e.message, ''); - } -} - -function sessionClose(value) { - close(); -} -// ========================== session end =========================== - -// ========================== settings begin ========================== -function increasePort(host, offset) { - function isIPv6(str) { - const ipv6Pattern = /^([0-9a-fA-F]{0,4}:){1,7}[0-9a-fA-F]{0,4}$/; - return ipv6Pattern.test(str); - } - - if (isIPv6(host)) { - if (host.startsWith('[')) { - let tmp = host.split(']:'); - if (tmp.length === 2) { - let port = parseInt(tmp[1]) || 0; - if (port > 0) { - return `${tmp[0]}]:${port + offset}`; - } - } - } - } else if (host.includes(':')) { - let tmp = host.split(':'); - if (tmp.length === 2) { - let port = parseInt(tmp[1]) || 0; - if (port > 0) { - return `${tmp[0]}:${port + offset}`; - } - } - } - return host; -} - -function getAlternativeCodecs() { - return JSON.stringify({ - vp8: true, - av1: false, - h264: false, - h265: false, - }); -} -// ========================== settings end =========================== - -// ========================== server begin ========================== -function getApiServer() { - const api_server = localStorage.getItem('api-server'); - if (api_server) { - return api_server; - } - - const custom_rendezvous_server = localStorage.getItem('custom-rendezvous-server'); - if (custom_rendezvous_server) { - let s = increasePort(custom_rendezvous_server, -2); - if (s == custom_rendezvous_server) { - return `http://${s}:${PORT - 2}`; - } else { - return `http://${s}`; - } - } - return 'https://admin.rustdesk.com'; -} - -function getAuditServer(typ) { - if (!localStorage.getItem("access_token")) { - return ''; - } - const api_server = getApiServer(); - if (!api_server || api_server.includes('rustdesk.com')) { - return ''; - } - return api_server + '/api/audit/' + typ; -} -// ========================== server end ============================ diff --git a/flutter/web/v1/README.md b/flutter/web/v1/README.md new file mode 100644 index 000000000..b9e2fc5c0 --- /dev/null +++ b/flutter/web/v1/README.md @@ -0,0 +1 @@ +v1 is not compatible with current Flutter source code. \ No newline at end of file diff --git a/flutter/web/index.html b/flutter/web/v1/index.html similarity index 100% rename from flutter/web/index.html rename to flutter/web/v1/index.html diff --git a/flutter/web/js/.gitattributes b/flutter/web/v1/js/.gitattributes similarity index 100% rename from flutter/web/js/.gitattributes rename to flutter/web/v1/js/.gitattributes diff --git a/flutter/web/js/.gitignore b/flutter/web/v1/js/.gitignore similarity index 100% rename from flutter/web/js/.gitignore rename to flutter/web/v1/js/.gitignore diff --git a/flutter/web/js/.yarnrc.yml b/flutter/web/v1/js/.yarnrc.yml similarity index 100% rename from flutter/web/js/.yarnrc.yml rename to flutter/web/v1/js/.yarnrc.yml diff --git a/flutter/web/js/gen_js_from_hbb.py b/flutter/web/v1/js/gen_js_from_hbb.py similarity index 100% rename from flutter/web/js/gen_js_from_hbb.py rename to flutter/web/v1/js/gen_js_from_hbb.py diff --git a/flutter/web/js/index.html b/flutter/web/v1/js/index.html similarity index 100% rename from flutter/web/js/index.html rename to flutter/web/v1/js/index.html diff --git a/flutter/web/js/package.json b/flutter/web/v1/js/package.json similarity index 70% rename from flutter/web/js/package.json rename to flutter/web/v1/js/package.json index 436806e8d..15e0e75b8 100644 --- a/flutter/web/js/package.json +++ b/flutter/web/v1/js/package.json @@ -3,19 +3,19 @@ "version": "1.0.0", "scripts": { "dev": "vite", - "build": "python ./gen_js_from_hbb.py > src/gen_js_from_hbb.ts && python ./ts_proto.py && tsc && vite build", + "build": "./gen_js_from_hbb.py > src/gen_js_from_hbb.ts && ./ts_proto.py && tsc && vite build", "preview": "vite preview" }, "devDependencies": { "typescript": "^4.4.4", - "vite": "2.8" + "vite": "^2.7.2" }, "dependencies": { "fast-sha256": "^1.3.0", "libsodium": "^0.7.9", "libsodium-wrappers": "^0.7.9", "pcm-player": "^0.0.11", - "ts-proto": "^1.169.1", + "ts-proto": "^1.101.0", "wasm-feature-detect": "^1.2.11", "zstddec": "^0.0.2" } diff --git a/flutter/web/js/src/codec.js b/flutter/web/v1/js/src/codec.js similarity index 95% rename from flutter/web/js/src/codec.js rename to flutter/web/v1/js/src/codec.js index 6b295adbd..27c9565ec 100644 --- a/flutter/web/js/src/codec.js +++ b/flutter/web/v1/js/src/codec.js @@ -25,7 +25,7 @@ export async function loadVp9(callback) { // Multithreading is used only if `options.threading` is true. // This requires browser support for the new `SharedArrayBuffer` and `Atomics` APIs, // currently available in Firefox and Chrome with experimental flags enabled. - // All major browsers disabled SharedArrayBuffer by default on January 5, 2018 + // 所有主流浏览器均默认于2018年1月5日禁用SharedArrayBuffer const isSIMD = await simd(); console.log('isSIMD: ' + isSIMD); window.OGVLoader.loadClass( diff --git a/flutter/web/js/src/common.ts b/flutter/web/v1/js/src/common.ts similarity index 100% rename from flutter/web/js/src/common.ts rename to flutter/web/v1/js/src/common.ts diff --git a/flutter/web/js/src/connection.ts b/flutter/web/v1/js/src/connection.ts similarity index 64% rename from flutter/web/js/src/connection.ts rename to flutter/web/v1/js/src/connection.ts index 297004bd8..b0c479c90 100644 --- a/flutter/web/js/src/connection.ts +++ b/flutter/web/v1/js/src/connection.ts @@ -4,10 +4,9 @@ import * as rendezvous from "./rendezvous.js"; import { loadVp9 } from "./codec"; import * as sha256 from "fast-sha256"; import * as globals from "./globals"; -import * as consts from "./consts"; import { decompress, mapKey, sleep } from "./common"; -export const PORT = 21116; +const PORT = 21116; const HOSTS = [ "rs-sg.rustdesk.com", "rs-cn.rustdesk.com", @@ -16,8 +15,8 @@ const HOSTS = [ let HOST = localStorage.getItem("rendezvous-server") || HOSTS[0]; const SCHEMA = "ws://"; -type MsgboxCallback = (type: string, title: string, text: string, link: string) => void; -type DrawCallback = (display: number, data: Uint8Array) => void; +type MsgboxCallback = (type: string, title: string, text: string) => void; +type DrawCallback = (data: Uint8Array) => void; //const cursorCanvas = document.createElement("canvas"); export default class Connection { @@ -67,7 +66,7 @@ export default class Connection { try { this._password = Uint8Array.from(JSON.parse("[" + p + "]")); } catch (e) { - console.error('Failed to get password, ' + e); + console.error(e); } } } @@ -171,7 +170,7 @@ export default class Connection { pk = undefined; } } catch (e) { - console.error('Failed to verify id pk, ', e); + console.error(e); pk = undefined; } if (!pk) @@ -196,7 +195,7 @@ export default class Connection { try { signedId = await globals.verify(signedId.id, Uint8Array.from(pk!)); } catch (e) { - console.error('Failed to verify signed id pk, ', e); + console.error(e); // fall back to non-secure connection in case pk mismatch console.error("pk mismatch, fall back to non-secure"); const public_key = message.PublicKey.fromPartial({}); @@ -243,12 +242,26 @@ export default class Connection { this.login(); } else if (msg?.test_delay) { const test_delay = msg?.test_delay; - console.log('test delay: ', test_delay); + console.log(test_delay); if (!test_delay.from_client) { this._ws?.sendMessage({ test_delay }); } } else if (msg?.login_response) { - this.handleLoginResponse(msg?.login_response); + const r = msg?.login_response; + if (r.error) { + if (r.error == "Wrong Password") { + this._password = undefined; + this.msgbox( + "re-input-password", + r.error, + "Do you want to enter again?" + ); + } else { + this.msgbox("error", "Login Error", r.error); + } + } else if (r.peer_info) { + this.handlePeerInfo(r.peer_info); + } } else if (msg?.video_frame) { this.handleVideoFrame(msg?.video_frame!); } else if (msg?.clipboard) { @@ -261,7 +274,7 @@ export default class Connection { try { globals.copyToClipboard(new TextDecoder().decode(cb.content)); } catch (e) { - console.error('Failed to copy to clipboard, ', e); + console.error(e); } // globals.pushEvent("clipboard", cb); } else if (msg?.cursor_data) { @@ -305,110 +318,13 @@ export default class Connection { } } - handleLoginResponse(response: message.LoginResponse) { - const loginErrorMap: Record = { - [consts.LOGIN_SCREEN_WAYLAND]: { - msgtype: "error", - title: "Login Error", - text: "Login screen using Wayland is not supported", - link: "https://rustdesk.com/docs/en/manual/linux/#login-screen", - try_again: true, - }, - [consts.LOGIN_MSG_DESKTOP_SESSION_NOT_READY]: { - msgtype: "session-login", - title: "", - text: "", - link: "", - try_again: true, - }, - [consts.LOGIN_MSG_DESKTOP_XSESSION_FAILED]: { - msgtype: "session-re-login", - title: "", - text: "", - link: "", - try_again: true, - }, - [consts.LOGIN_MSG_DESKTOP_SESSION_ANOTHER_USER]: { - msgtype: "info-nocancel", - title: "another_user_login_title_tip", - text: "another_user_login_text_tip", - link: "", - try_again: false, - }, - [consts.LOGIN_MSG_DESKTOP_XORG_NOT_FOUND]: { - msgtype: "info-nocancel", - title: "xorg_not_found_title_tip", - text: "xorg_not_found_text_tip", - link: "https://rustdesk.com/docs/en/manual/linux/#login-screen", - try_again: true, - }, - [consts.LOGIN_MSG_DESKTOP_NO_DESKTOP]: { - msgtype: "info-nocancel", - title: "no_desktop_title_tip", - text: "no_desktop_text_tip", - link: "https://rustdesk.com/docs/en/manual/linux/#login-screen", - try_again: true, - }, - [consts.LOGIN_MSG_DESKTOP_SESSION_NOT_READY_PASSWORD_EMPTY]: { - msgtype: "session-login-password", - title: "", - text: "", - link: "", - try_again: true, - }, - [consts.LOGIN_MSG_DESKTOP_SESSION_NOT_READY_PASSWORD_WRONG]: { - msgtype: "session-login-re-password", - title: "", - text: "", - link: "", - try_again: true, - }, - [consts.LOGIN_MSG_NO_PASSWORD_ACCESS]: { - msgtype: "wait-remote-accept-nook", - title: "Prompt", - text: "Please wait for the remote side to accept your session request...", - link: "", - try_again: true, - }, - }; - - const err = response.error; - if (err) { - if (err == consts.LOGIN_MSG_PASSWORD_EMPTY) { - this._password = undefined; - this.msgbox("input-password", "Password Required", "", ""); - } - if (err == consts.LOGIN_MSG_PASSWORD_WRONG) { - this._password = undefined; - this.msgbox( - "re-input-password", - err, - "Do you want to enter again?" - ); - } else if (err == consts.LOGIN_MSG_2FA_WRONG || err == consts.REQUIRE_2FA) { - this.msgbox("input-2fa", err, ""); - } else if (err in loginErrorMap) { - const m = loginErrorMap[err]; - this.msgbox(m.msgtype, m.title, m.text, m.link); - } else { - if (err.includes(consts.SCRAP_X11_REQUIRED)) { - this.msgbox("error", "Login Error", err, consts.SCRAP_X11_REF_URL); - } else { - this.msgbox("error", "Login Error", err); - } - } - } else if (response.peer_info) { - this.handlePeerInfo(response.peer_info); - } + msgbox(type_: string, title: string, text: string) { + this._msgbox?.(type_, title, text); } - msgbox(type_: string, title: string, text: string, link: string = '') { - this._msgbox?.(type_, title, text, link); - } - - draw(display: number, frame: any) { - this._draw?.(display, frame); - globals.draw(display, frame); + draw(frame: any) { + this._draw?.(frame); + globals.draw(frame); } close() { @@ -431,55 +347,38 @@ export default class Connection { this._draw = callback; } - login(info?: { - os_login?: message.OSLogin, - password?: Uint8Array - }) { - if (info?.password) { + login(password: string | undefined = undefined) { + if (password) { const salt = this._hash?.salt; - let p = hash([info.password, salt!]); + let p = hash([password, salt!]); this._password = p; const challenge = this._hash?.challenge; p = hash([p, challenge!]); this.msgbox("connecting", "Connecting...", "Logging in..."); - this._sendLoginMessage({ os_login: info.os_login, password: p }); + this._sendLoginMessage(p); } else { let p = this._password; if (p) { const challenge = this._hash?.challenge; p = hash([p, challenge!]); } - this._sendLoginMessage({ os_login: info?.os_login, password: p }); + this._sendLoginMessage(p); } } - changePreferCodec() { - const supported_decoding = message.SupportedDecoding.fromPartial({ - ability_vp9: 1, - ability_h264: 1, - }); - const option = message.OptionMessage.fromPartial({ supported_decoding }); - const misc = message.Misc.fromPartial({ option }); - this._ws?.sendMessage({ misc }); - } - async reconnect() { this.close(); await this.start(this._id); } - _sendLoginMessage(login: { - os_login?: message.OSLogin, - password?: Uint8Array, - }) { + _sendLoginMessage(password: Uint8Array | undefined = undefined) { const login_request = message.LoginRequest.fromPartial({ username: this._id!, my_id: "web", // to-do my_name: "web", // to-do - password: login.password, + password, option: this.getOptionMessage(), video_ack_required: true, - os_login: login.os_login, }); this._ws?.sendMessage({ login_request }); } @@ -536,7 +435,7 @@ export default class Connection { i++; if (i == n) this.sendVideoReceived(); if (ok && dec.frameBuffer && n == i) { - this.draw(vf.display, dec.frameBuffer); + this.draw(dec.frameBuffer); const now = new Date().getTime(); var elapsed = now - tm; this._videoTestSpeed[1] += elapsed; @@ -544,9 +443,9 @@ export default class Connection { if (this._videoTestSpeed[0] >= 30) { console.log( "video decoder: " + - parseInt( - "" + this._videoTestSpeed[1] / this._videoTestSpeed[0] - ) + parseInt( + "" + this._videoTestSpeed[1] / this._videoTestSpeed[0] + ) ); this._videoTestSpeed = [0, 0]; } @@ -557,17 +456,8 @@ export default class Connection { } handlePeerInfo(pi: message.PeerInfo) { - localStorage.setItem('last_remote_id', this._id); this._peerInfo = pi; - if (pi.current_display > pi.displays.length) { - pi.current_display = 0; - } - if (globals.getVersionNumber(pi.version) < globals.getVersionNumber("1.1.10")) { - this.setPermission("restart", false); - } if (pi.displays.length == 0) { - this.setOption("info", pi); - globals.pushEvent("update_privacy_mode", {}); this.msgbox("error", "Remote Error", "No Display"); return; } @@ -577,7 +467,6 @@ export default class Connection { if (p) this.inputOsPassword(p); const username = this.getOption("info")?.username; if (username && !pi.username) pi.username = username; - globals.pushEvent("update_privacy_mode", {}); this.setOption("info", pi); if (this.getRemember()) { if (this._password?.length) { @@ -592,10 +481,6 @@ export default class Connection { } } - setPermission(name: string, value: Boolean) { - globals.pushEvent("permission", { [name]: value }); - } - shouldAutoLogin(): string { const l = this.getOption("lock-after-session-end"); const a = !!this.getOption("auto-login"); @@ -631,7 +516,7 @@ export default class Connection { default: return; } - this.setPermission(name, p.enabled); + globals.pushEvent("permission", { [name]: p.enabled }); } else if (misc.switch_display) { this.loadVideoDecoder(); globals.pushEvent("switch_display", misc.switch_display); @@ -652,27 +537,7 @@ export default class Connection { } getOption(name: string): any { - return this._options[name] ?? globals.getUserDefaultOption(name); - } - - getToggleOption(name: string): Boolean { - // TODO: more default settings - const defaultToggleTrue = [ - 'show-remote-cursor', - 'privacy-mode', - 'enable-file-copy-paste', - 'allow_swap_key', - ]; - return this._options[name] || (defaultToggleTrue.includes(name) ? true : false); - } - - // TODO: - getStatus(): String { - return JSON.stringify({ status_num: 10 }); - } - - // TODO: - checkConnStatus() { + return this._options[name]; } setOption(name: string, value: any) { @@ -727,70 +592,17 @@ export default class Connection { this._ws?.sendMessage({ key_event }); } - restart() { - const misc = message.Misc.fromPartial({}); - misc.restart_remote_device = true; - this._ws?.sendMessage({ misc }); - } - inputString(seq: string) { const key_event = message.KeyEvent.fromPartial({ seq }); this._ws?.sendMessage({ key_event }); } - send2fa(code: string) { - const auth_2fa = message.Auth2FA.fromPartial({ code }); - this._ws?.sendMessage({ auth_2fa }); - } - - _captureDisplays({ add, sub, set }: { - add?: number[], sub?: number[], set?: number[] - }) { - const capture_displays = message.CaptureDisplays.fromPartial({ add, sub, set }); - const misc = message.Misc.fromPartial({ capture_displays }); + switchDisplay(display: number) { + const switch_display = message.SwitchDisplay.fromPartial({ display }); + const misc = message.Misc.fromPartial({ switch_display }); this._ws?.sendMessage({ misc }); } - switchDisplay(v: string) { - try { - const obj = JSON.parse(v); - const value = obj.value; - const isDesktop = obj.isDesktop; - if (value.length == 1) { - const switch_display = message.SwitchDisplay.fromPartial({ display: value[0] }); - const misc = message.Misc.fromPartial({ switch_display }); - this._ws?.sendMessage({ misc }); - - if (!isDesktop) { - this._captureDisplays({ set: value }); - } else { - // If support merging images, check_remove_unused_displays() in ui_session_interface.rs - } - } else { - this._captureDisplays({ set: value }); - } - } - catch (e) { - console.log('Failed to switch display, invalid param "' + v + '"'); - } - } - - elevateWithLogon(value: string) { - try { - const obj = JSON.parse(value); - const logon = message.ElevationRequestWithLogon.fromPartial({ - username: obj.username, - password: obj.password - }); - const elevation_request = message.ElevationRequest.fromPartial({ logon }); - const misc = message.Misc.fromPartial({ elevation_request }); - this._ws?.sendMessage({ misc }); - } - catch (e) { - console.log('Failed to elevate with logon, invalid param "' + value + '"'); - } - } - async inputOsPassword(seq: string) { this.inputMouse(); await sleep(50); @@ -839,52 +651,6 @@ export default class Connection { } toggleOption(name: string) { - - // } else if name == "block-input" { - // option.block_input = BoolOption::Yes.into(); - // } else if name == "unblock-input" { - // option.block_input = BoolOption::No.into(); - // } else if name == "show-quality-monitor" { - // config.show_quality_monitor.v = !config.show_quality_monitor.v; - // } else if name == "allow_swap_key" { - // config.allow_swap_key.v = !config.allow_swap_key.v; - // } else if name == "view-only" { - // config.view_only.v = !config.view_only.v; - // let f = |b: bool| { - // if b { - // BoolOption::Yes.into() - // } else { - // BoolOption::No.into() - // } - // }; - // if config.view_only.v { - // option.disable_keyboard = f(true); - // option.disable_clipboard = f(true); - // option.show_remote_cursor = f(true); - // option.enable_file_transfer = f(false); - // option.lock_after_session_end = f(false); - // } else { - // option.disable_keyboard = f(false); - // option.disable_clipboard = f(self.get_toggle_option("disable-clipboard")); - // option.show_remote_cursor = f(self.get_toggle_option("show-remote-cursor")); - // option.enable_file_transfer = f(self.config.enable_file_transfer.v); - // option.lock_after_session_end = f(self.config.lock_after_session_end.v); - // } - // } else { - // let is_set = self - // .options - // .get(&name) - // .map(|o| !o.is_empty()) - // .unwrap_or(false); - // if is_set { - // self.config.options.remove(&name); - // } else { - // self.config.options.insert(name, "Y".to_owned()); - // } - // self.config.store(&self.id); - // return None; - // } - const v = !this._options[name]; const option = message.OptionMessage.fromPartial({}); const v2 = v @@ -906,43 +672,13 @@ export default class Connection { case "privacy-mode": option.privacy_mode = v2; break; - case "enable-file-copy-paste": - option.enable_file_transfer = v2; - break; case "block-input": option.block_input = message.OptionMessage_BoolOption.Yes; break; case "unblock-input": option.block_input = message.OptionMessage_BoolOption.No; break; - case "show-quality-monitor": - case "allow-swap-key": - break; - case "view-only": - if (v) { - option.disable_keyboard = message.OptionMessage_BoolOption.Yes; - option.disable_clipboard = message.OptionMessage_BoolOption.Yes; - option.show_remote_cursor = message.OptionMessage_BoolOption.Yes; - option.enable_file_transfer = message.OptionMessage_BoolOption.No; - option.lock_after_session_end = message.OptionMessage_BoolOption.No; - } else { - option.disable_keyboard = message.OptionMessage_BoolOption.No; - option.disable_clipboard = this.getToggleOption("disable-clipboard") - ? message.OptionMessage_BoolOption.Yes - : message.OptionMessage_BoolOption.No; - option.show_remote_cursor = this.getToggleOption("show-remote-cursor") - ? message.OptionMessage_BoolOption.Yes - : message.OptionMessage_BoolOption.No; - option.enable_file_transfer = this.getToggleOption("enable-file-copy-paste") - ? message.OptionMessage_BoolOption.Yes - : message.OptionMessage_BoolOption.No; - option.lock_after_session_end = this.getToggleOption("lock-after-session-end") - ? message.OptionMessage_BoolOption.Yes - : message.OptionMessage_BoolOption.No; - } - break; default: - this.setOption(name, this._options[name] ? undefined : "Y"); return; } if (name.indexOf("block-input") < 0) this.setOption(name, v); @@ -950,20 +686,6 @@ export default class Connection { this._ws?.sendMessage({ misc }); } - togglePrivacyMode(value: string) { - try { - const obj = JSON.parse(value); - const toggle_privacy_mode = message.TogglePrivacyMode.fromPartial({ - impl_key: obj.impl_key, - on: obj.on, - }); - const misc = message.Misc.fromPartial({ toggle_privacy_mode }); - this._ws?.sendMessage({ misc }); - } catch (e) { - console.log('Failed to toggle privacy mode, invalid param "' + value + '"') - } - } - getImageQuality() { return this.getOption("image-quality"); } @@ -998,7 +720,7 @@ export default class Connection { loadVp9((decoder: any) => { this._videoDecoder = decoder; console.log("vp9 loaded"); - console.log('The decoder: ', decoder); + console.log(decoder); }); } } diff --git a/flutter/web/v1/js/src/globals.js b/flutter/web/v1/js/src/globals.js new file mode 100644 index 000000000..953add18d --- /dev/null +++ b/flutter/web/v1/js/src/globals.js @@ -0,0 +1,383 @@ +import Connection from "./connection"; +import _sodium from "libsodium-wrappers"; +import { CursorData } from "./message"; +import { loadVp9 } from "./codec"; +import { checkIfRetry, version } from "./gen_js_from_hbb"; +import { initZstd, translate } from "./common"; +import PCMPlayer from "pcm-player"; + +window.curConn = undefined; +window.isMobile = () => { + return /(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|ipad|iris|kindle|Android|Silk|lge |maemo|midp|mmp|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i.test(navigator.userAgent) + || /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(navigator.userAgent.substr(0, 4)); +} + +export function isDesktop() { + return !isMobile(); +} + +export function msgbox(type, title, text) { + if (!type || (type == 'error' && !text)) return; + const text2 = text.toLowerCase(); + var hasRetry = checkIfRetry(type, title, text) ? 'true' : ''; + onGlobalEvent(JSON.stringify({ name: 'msgbox', type, title, text, hasRetry })); +} + +function jsonfyForDart(payload) { + var tmp = {}; + for (const [key, value] of Object.entries(payload)) { + if (!key) continue; + tmp[key] = value instanceof Uint8Array ? '[' + value.toString() + ']' : JSON.stringify(value); + } + return tmp; +} + +export function pushEvent(name, payload) { + payload = jsonfyForDart(payload); + payload.name = name; + onGlobalEvent(JSON.stringify(payload)); +} + +let yuvWorker; +let yuvCanvas; +let gl; +let pixels; +let flipPixels; +let oldSize; +if (YUVCanvas.WebGLFrameSink.isAvailable()) { + var canvas = document.createElement('canvas'); + yuvCanvas = YUVCanvas.attach(canvas, { webGL: true }); + gl = canvas.getContext("webgl"); +} else { + yuvWorker = new Worker("./yuv.js"); +} +let testSpeed = [0, 0]; + +export function draw(frame) { + if (yuvWorker) { + // frame's (y/u/v).bytes already detached, can not transferrable any more. + yuvWorker.postMessage(frame); + } else { + var tm0 = new Date().getTime(); + yuvCanvas.drawFrame(frame); + var width = canvas.width; + var height = canvas.height; + var size = width * height * 4; + if (size != oldSize) { + pixels = new Uint8Array(size); + flipPixels = new Uint8Array(size); + oldSize = size; + } + gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, pixels); + const row = width * 4; + const end = (height - 1) * row; + for (let i = 0; i < size; i += row) { + flipPixels.set(pixels.subarray(i, i + row), end - i); + } + onRgba(flipPixels); + testSpeed[1] += new Date().getTime() - tm0; + testSpeed[0] += 1; + if (testSpeed[0] > 30) { + console.log('gl: ' + parseInt('' + testSpeed[1] / testSpeed[0])); + testSpeed = [0, 0]; + } + } + /* + var testCanvas = document.getElementById("test-yuv-decoder-canvas"); + if (testCanvas && currentFrame) { + var ctx = testCanvas.getContext("2d"); + testCanvas.width = frame.format.displayWidth; + testCanvas.height = frame.format.displayHeight; + var img = ctx.createImageData(testCanvas.width, testCanvas.height); + img.data.set(currentFrame); + ctx.putImageData(img, 0, 0); + } + */ +} + +export function sendOffCanvas(c) { + let canvas = c.transferControlToOffscreen(); + yuvWorker.postMessage({ canvas }, [canvas]); +} + +export function setConn(conn) { + window.curConn = conn; +} + +export function getConn() { + return window.curConn; +} + +export async function startConn(id) { + setByName('remote_id', id); + await curConn.start(id); +} + +export function close() { + getConn()?.close(); + setConn(undefined); +} + +export function newConn() { + window.curConn?.close(); + const conn = new Connection(); + setConn(conn); + return conn; +} + +let sodium; +export async function verify(signed, pk) { + if (!sodium) { + await _sodium.ready; + sodium = _sodium; + } + if (typeof pk == 'string') { + pk = decodeBase64(pk); + } + return sodium.crypto_sign_open(signed, pk); +} + +export function decodeBase64(pk) { + return sodium.from_base64(pk, sodium.base64_variants.ORIGINAL); +} + +export function genBoxKeyPair() { + const pair = sodium.crypto_box_keypair(); + const sk = pair.privateKey; + const pk = pair.publicKey; + return [sk, pk]; +} + +export function genSecretKey() { + return sodium.crypto_secretbox_keygen(); +} + +export function seal(unsigned, theirPk, ourSk) { + const nonce = Uint8Array.from(Array(24).fill(0)); + return sodium.crypto_box_easy(unsigned, nonce, theirPk, ourSk); +} + +function makeOnce(value) { + var byteArray = Array(24).fill(0); + + for (var index = 0; index < byteArray.length && value > 0; index++) { + var byte = value & 0xff; + byteArray[index] = byte; + value = (value - byte) / 256; + } + + return Uint8Array.from(byteArray); +}; + +export function encrypt(unsigned, nonce, key) { + return sodium.crypto_secretbox_easy(unsigned, makeOnce(nonce), key); +} + +export function decrypt(signed, nonce, key) { + return sodium.crypto_secretbox_open_easy(signed, makeOnce(nonce), key); +} + +window.setByName = (name, value) => { + switch (name) { + case 'remote_id': + localStorage.setItem('remote-id', value); + break; + case 'connect': + newConn(); + startConn(value); + break; + case 'login': + value = JSON.parse(value); + curConn.setRemember(value.remember == 'true'); + curConn.login(value.password); + break; + case 'close': + close(); + break; + case 'refresh': + curConn.refresh(); + break; + case 'reconnect': + curConn.reconnect(); + break; + case 'toggle_option': + curConn.toggleOption(value); + break; + case 'image_quality': + curConn.setImageQuality(value); + break; + case 'lock_screen': + curConn.lockScreen(); + break; + case 'ctrl_alt_del': + curConn.ctrlAltDel(); + break; + case 'switch_display': + curConn.switchDisplay(value); + break; + case 'remove': + const peers = getPeers(); + delete peers[value]; + localStorage.setItem('peers', JSON.stringify(peers)); + break; + case 'input_key': + value = JSON.parse(value); + curConn.inputKey(value.name, value.down == 'true', value.press == 'true', value.alt == 'true', value.ctrl == 'true', value.shift == 'true', value.command == 'true'); + break; + case 'input_string': + curConn.inputString(value); + break; + case 'send_mouse': + let mask = 0; + value = JSON.parse(value); + switch (value.type) { + case 'down': + mask = 1; + break; + case 'up': + mask = 2; + break; + case 'wheel': + mask = 3; + break; + } + switch (value.buttons) { + case 'left': + mask |= 1 << 3; + break; + case 'right': + mask |= 2 << 3; + break; + case 'wheel': + mask |= 4 << 3; + } + curConn.inputMouse(mask, parseInt(value.x || '0'), parseInt(value.y || '0'), value.alt == 'true', value.ctrl == 'true', value.shift == 'true', value.command == 'true'); + break; + case 'option': + value = JSON.parse(value); + localStorage.setItem(value.name, value.value); + break; + case 'peer_option': + value = JSON.parse(value); + curConn.setOption(value.name, value.value); + break; + case 'input_os_password': + curConn.inputOsPassword(value); + break; + default: + break; + } +} + +window.getByName = (name, arg) => { + let v = _getByName(name, arg); + if (typeof v == 'string' || v instanceof String) return v; + if (v == undefined || v == null) return ''; + return JSON.stringify(v); +} + +function getPeersForDart() { + const peers = []; + for (const [id, value] of Object.entries(getPeers())) { + if (!id) continue; + const tm = value['tm']; + const info = value['info']; + if (!tm || !info) continue; + peers.push([tm, id, info]); + } + return peers.sort().reverse().map(x => x.slice(1)); +} + +function _getByName(name, arg) { + switch (name) { + case 'peers': + return getPeersForDart(); + case 'remote_id': + return localStorage.getItem('remote-id'); + case 'remember': + return curConn.getRemember(); + case 'toggle_option': + return curConn.getOption(arg) || false; + case 'option': + return localStorage.getItem(arg); + case 'image_quality': + return curConn.getImageQuality(); + case 'translate': + arg = JSON.parse(arg); + return translate(arg.locale, arg.text); + case 'peer_option': + return curConn.getOption(arg); + case 'test_if_valid_server': + break; + case 'version': + return version; + } + return ''; +} + +let opusWorker = new Worker("./libopus.js"); +let pcmPlayer; + +export function initAudio(channels, sampleRate) { + pcmPlayer = newAudioPlayer(channels, sampleRate); + opusWorker.postMessage({ channels, sampleRate }); +} + +export function playAudio(packet) { + opusWorker.postMessage(packet, [packet.buffer]); +} + +window.init = async () => { + if (yuvWorker) { + yuvWorker.onmessage = (e) => { + onRgba(e.data); + } + } + opusWorker.onmessage = (e) => { + pcmPlayer.feed(e.data); + } + loadVp9(() => { }); + await initZstd(); + console.log('init done'); +} + +export function getPeers() { + try { + return JSON.parse(localStorage.getItem('peers')) || {}; + } catch (e) { + return {}; + } +} + +function newAudioPlayer(channels, sampleRate) { + return new PCMPlayer({ + channels, + sampleRate, + flushingTime: 2000 + }); +} + +export function copyToClipboard(text) { + if (window.clipboardData && window.clipboardData.setData) { + // Internet Explorer-specific code path to prevent textarea being shown while dialog is visible. + return window.clipboardData.setData("Text", text); + + } + else if (document.queryCommandSupported && document.queryCommandSupported("copy")) { + var textarea = document.createElement("textarea"); + textarea.textContent = text; + textarea.style.position = "fixed"; // Prevent scrolling to bottom of page in Microsoft Edge. + document.body.appendChild(textarea); + textarea.select(); + try { + return document.execCommand("copy"); // Security exception may be thrown by some browsers. + } + catch (ex) { + console.warn("Copy to clipboard failed.", ex); + // return prompt("Copy to clipboard: Ctrl+C, Enter", text); + } + finally { + document.body.removeChild(textarea); + } + } +} \ No newline at end of file diff --git a/flutter/web/js/src/main.ts b/flutter/web/v1/js/src/main.ts similarity index 100% rename from flutter/web/js/src/main.ts rename to flutter/web/v1/js/src/main.ts diff --git a/flutter/web/js/src/style.css b/flutter/web/v1/js/src/style.css similarity index 100% rename from flutter/web/js/src/style.css rename to flutter/web/v1/js/src/style.css diff --git a/flutter/web/js/src/ui.js b/flutter/web/v1/js/src/ui.js similarity index 100% rename from flutter/web/js/src/ui.js rename to flutter/web/v1/js/src/ui.js diff --git a/flutter/web/js/src/vite-env.d.ts b/flutter/web/v1/js/src/vite-env.d.ts similarity index 100% rename from flutter/web/js/src/vite-env.d.ts rename to flutter/web/v1/js/src/vite-env.d.ts diff --git a/flutter/web/js/src/websock.ts b/flutter/web/v1/js/src/websock.ts similarity index 100% rename from flutter/web/js/src/websock.ts rename to flutter/web/v1/js/src/websock.ts diff --git a/flutter/web/js/ts_proto.py b/flutter/web/v1/js/ts_proto.py similarity index 100% rename from flutter/web/js/ts_proto.py rename to flutter/web/v1/js/ts_proto.py diff --git a/flutter/web/js/tsconfig.json b/flutter/web/v1/js/tsconfig.json similarity index 100% rename from flutter/web/js/tsconfig.json rename to flutter/web/v1/js/tsconfig.json diff --git a/flutter/web/js/vite.config.js b/flutter/web/v1/js/vite.config.js similarity index 100% rename from flutter/web/js/vite.config.js rename to flutter/web/v1/js/vite.config.js diff --git a/flutter/web/js/yarn.lock b/flutter/web/v1/js/yarn.lock similarity index 100% rename from flutter/web/js/yarn.lock rename to flutter/web/v1/js/yarn.lock diff --git a/flutter/web/libs/firebase-analytics.js b/flutter/web/v1/libs/firebase-analytics.js similarity index 100% rename from flutter/web/libs/firebase-analytics.js rename to flutter/web/v1/libs/firebase-analytics.js diff --git a/flutter/web/libs/firebase-app.js b/flutter/web/v1/libs/firebase-app.js similarity index 100% rename from flutter/web/libs/firebase-app.js rename to flutter/web/v1/libs/firebase-app.js diff --git a/flutter/web/manifest.json b/flutter/web/v1/manifest.json similarity index 100% rename from flutter/web/manifest.json rename to flutter/web/v1/manifest.json diff --git a/flutter/web/yarn.lock b/flutter/web/v1/yarn.lock similarity index 100% rename from flutter/web/yarn.lock rename to flutter/web/v1/yarn.lock diff --git a/flutter/web/yuv.js b/flutter/web/v1/yuv.js similarity index 100% rename from flutter/web/yuv.js rename to flutter/web/v1/yuv.js diff --git a/flutter/web/yuv.wasm b/flutter/web/v1/yuv.wasm similarity index 100% rename from flutter/web/yuv.wasm rename to flutter/web/v1/yuv.wasm diff --git a/flutter/web/v2/README.md b/flutter/web/v2/README.md new file mode 100644 index 000000000..7c128776c --- /dev/null +++ b/flutter/web/v2/README.md @@ -0,0 +1 @@ +Under dev. \ No newline at end of file