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 2ff3bb45c..000000000 Binary files a/flutter/web/icons/Icon-192.png and /dev/null differ diff --git a/flutter/web/icons/Icon-512.png b/flutter/web/icons/Icon-512.png deleted file mode 100644 index 4d94e3123..000000000 Binary files a/flutter/web/icons/Icon-512.png and /dev/null differ diff --git a/flutter/web/icons/Icon-maskable-192.png b/flutter/web/icons/Icon-maskable-192.png deleted file mode 100644 index 2ff3bb45c..000000000 Binary files a/flutter/web/icons/Icon-maskable-192.png and /dev/null differ diff --git a/flutter/web/icons/Icon-maskable-512.png b/flutter/web/icons/Icon-maskable-512.png deleted file mode 100644 index 4d94e3123..000000000 Binary files a/flutter/web/icons/Icon-maskable-512.png and /dev/null differ diff --git a/flutter/web/js/src/consts.ts b/flutter/web/js/src/consts.ts deleted file mode 100644 index fe7d534e5..000000000 --- a/flutter/web/js/src/consts.ts +++ /dev/null @@ -1,19 +0,0 @@ -export const LOGIN_MSG_DESKTOP_SESSION_NOT_READY = 'Desktop session not ready'; -export const LOGIN_MSG_DESKTOP_XSESSION_FAILED = 'Desktop xsession failed'; -export const LOGIN_MSG_DESKTOP_SESSION_ANOTHER_USER = 'Desktop session another user login'; -export const LOGIN_MSG_DESKTOP_XORG_NOT_FOUND = 'Desktop xorg not found'; -// ls /usr/share/xsessions/ -export const LOGIN_MSG_DESKTOP_NO_DESKTOP = 'Desktop none'; -export const LOGIN_MSG_DESKTOP_SESSION_NOT_READY_PASSWORD_EMPTY = - 'Desktop session not ready, password empty'; -export const LOGIN_MSG_DESKTOP_SESSION_NOT_READY_PASSWORD_WRONG = - 'Desktop session not ready, password wrong'; -export const LOGIN_MSG_PASSWORD_EMPTY = 'Empty Password'; -export const LOGIN_MSG_PASSWORD_WRONG = 'Wrong Password'; -export const LOGIN_MSG_2FA_WRONG = 'Wrong 2FA Code'; -export const REQUIRE_2FA = '2FA Required'; -export const LOGIN_MSG_NO_PASSWORD_ACCESS = 'No Password Access'; -export const LOGIN_MSG_OFFLINE = 'Offline'; -export const LOGIN_SCREEN_WAYLAND = 'Wayland login screen is not supported'; -export const SCRAP_X11_REQUIRED = 'x11 expected'; -export const SCRAP_X11_REF_URL = 'https://rustdesk.com/docs/en/manual/linux/#x11-required'; diff --git a/flutter/web/js/src/globals.js b/flutter/web/js/src/globals.js deleted file mode 100644 index 5c2823dbc..000000000 --- a/flutter/web/js/src/globals.js +++ /dev/null @@ -1,792 +0,0 @@ -import Connection from "./connection"; -import PORT 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, 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