mirror of
https://github.com/rustdesk/rustdesk.git
synced 2024-11-23 19:49:05 +08:00
split web js to v1 and v2
This commit is contained in:
parent
3742b51d58
commit
41a20b50ea
1
.github/workflows/flutter-build.yml
vendored
1
.github/workflows/flutter-build.yml
vendored
@ -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:
|
||||
|
@ -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
|
@ -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 |
@ -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';
|
@ -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
1
flutter/web/v1/README.md
Normal file
@ -0,0 +1 @@
|
||||
v1 is not compatible with current Flutter source code.
|
@ -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"
|
||||
}
|
@ -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(
|
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
383
flutter/web/v1/js/src/globals.js
Normal file
383
flutter/web/v1/js/src/globals.js
Normal 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
1
flutter/web/v2/README.md
Normal file
@ -0,0 +1 @@
|
||||
Under dev.
|
Loading…
Reference in New Issue
Block a user