split web js to v1 and v2

This commit is contained in:
rustdesk 2024-06-22 12:29:20 +08:00
parent 3742b51d58
commit 41a20b50ea
37 changed files with 435 additions and 1156 deletions

View File

@ -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:

View File

@ -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

View File

@ -1 +0,0 @@
<svg viewBox="0 0 375 375" style="width:32px;height:32px;margin:0 4px 4px 0" xmlns="http://www.w3.org/2000/svg"><rect transform="matrix(.91553 0 0 .91553 -152.92 116.76)" x="167.03" y="-127.54" width="409.6" height="409.6" rx="64" ry="64" fill="#0071ff"></rect><path d="M150.428 322.264c-29.063-6.202-53.897-22.439-73.115-47.804-19.507-25.746-27.838-55.355-25.723-91.414 6.655-62.013 47.667-106.753 99.687-120.411 4.509-.989 8.353-3.462 12.55-1.322 3.22 1.64 6.028 4.467 7.206 7.251 1.25 2.955 1.877 21.54.99 29.331-1.076 9.46-3.877 12.418-14.566 15.388-29.723 10.195-48.105 34.07-53.697 61.017-4.8 29.668 2.951 59.729 21.528 78.727 8.966 8.993 17.92 14.24 30.869 18.086 8.646 2.57 13.393 5.758 15.036 10.102 1.085 2.867 1.63 22.984.779 28.772-1.33 9.046-1.702 9.796-5.792 11.667-5.029 2.3-7.404 2.392-15.752.61zm50.708.29c-3.092-1.402-5.673-4.83-6.73-8.94-.134-9.408-2.366-25.754 1.02-33.373 1.88-4.128 4.65-5.999 12.433-8.396 21.267-6.551 37.593-19.88 46.806-38.213 11.11-22.108 11.877-55.183 1.808-77.975-9.154-20.723-25.7-35.217-48.555-42.534-8.872-2.84-12.004-5.065-12.968-9.21-1.002-4.31-1.435-19.87-.785-28.218.682-8.766 1.249-9.99 6.162-13.318 3.701-2.505 5.482-2.446 17.223.575 36.718 10.077 65.97 33.597 83.026 66.68 18.495 37.034 19.191 86.11 1.742 122.655-17.233 36.09-50.591 62.511-88.622 70.194-8.172 1.65-9.07 1.656-12.56.073z" fill="#fff"></path></svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

View File

@ -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';

View File

@ -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 ============================

1
flutter/web/v1/README.md Normal file
View File

@ -0,0 +1 @@
v1 is not compatible with current Flutter source code.

View File

@ -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"
}

View File

@ -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(

View File

@ -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<string, any> = {
[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);
});
}
}

View File

@ -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);
}
}
}

1
flutter/web/v2/README.md Normal file
View File

@ -0,0 +1 @@
Under dev.