2022-01-20 12:49:57 +08:00
|
|
|
import Connection from "./connection";
|
|
|
|
import _sodium from "libsodium-wrappers";
|
2022-01-26 18:58:55 +08:00
|
|
|
import * as zstd from 'zstddec';
|
|
|
|
import { CursorData } from "./message";
|
2022-01-27 18:58:29 +08:00
|
|
|
import { loadOpus, loadVp9 } from "./codec";
|
2022-01-26 12:39:44 +08:00
|
|
|
|
2022-01-27 18:58:29 +08:00
|
|
|
var decompressor;
|
2022-01-28 03:53:23 +08:00
|
|
|
var wasmExports;
|
2022-01-26 12:39:44 +08:00
|
|
|
|
|
|
|
var currentFrame = undefined;
|
|
|
|
var events = [];
|
2022-01-20 12:49:57 +08:00
|
|
|
|
2022-01-26 18:58:55 +08:00
|
|
|
window.curConn = undefined;
|
2022-01-27 23:32:51 +08:00
|
|
|
window.getRgba = () => {
|
|
|
|
const tmp = currentFrame;
|
|
|
|
currentFrame = undefined;
|
|
|
|
return tmp || null;
|
|
|
|
}
|
2022-01-26 12:39:44 +08:00
|
|
|
window.getLanguage = () => navigator.language;
|
|
|
|
|
|
|
|
export function msgbox(type, title, text) {
|
2022-01-27 13:24:40 +08:00
|
|
|
if (!events) return;
|
2022-01-28 18:20:52 +08:00
|
|
|
if (!type || (type == 'error' && !text)) return;
|
2022-01-27 01:30:29 +08:00
|
|
|
const text2 = text.toLowerCase();
|
|
|
|
var hasRetry = type == "error"
|
2022-01-26 12:39:44 +08:00
|
|
|
&& title == "Connection Error"
|
2022-01-27 01:30:29 +08:00
|
|
|
&& text2.indexOf("offline") < 0
|
|
|
|
&& text2.indexOf("exist") < 0
|
|
|
|
&& text2.indexOf("handshake") < 0
|
|
|
|
&& text2.indexOf("failed") < 0
|
|
|
|
&& text2.indexOf("resolve") < 0
|
|
|
|
&& text2.indexOf("mismatch") < 0
|
|
|
|
&& text2.indexOf("manually") < 0;
|
2022-01-26 12:39:44 +08:00
|
|
|
events.push({ name: 'msgbox', type, title, text, hasRetry });
|
|
|
|
}
|
|
|
|
|
2022-01-28 18:20:52 +08:00
|
|
|
function jsonfyForDart(payload) {
|
|
|
|
var tmp = {};
|
|
|
|
for (const [key, value] of Object.entries(payload)) {
|
|
|
|
if (!key) continue;
|
|
|
|
var newName = '';
|
|
|
|
for (var i = 0; i < key.length; ++i) {
|
|
|
|
var ch = key[i];
|
|
|
|
if (ch.toUpperCase() == ch) {
|
|
|
|
newName += '_' + ch.toLowerCase();
|
|
|
|
} else {
|
|
|
|
newName += ch;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
tmp[newName] = value instanceof Uint8Array ? '[' + value.toString() + ']' : JSON.stringify(value);
|
|
|
|
}
|
|
|
|
return tmp;
|
|
|
|
}
|
|
|
|
|
2022-01-26 12:39:44 +08:00
|
|
|
export function pushEvent(name, payload) {
|
2022-01-27 13:24:40 +08:00
|
|
|
if (!events) return;
|
2022-01-29 11:31:05 +08:00
|
|
|
payload = jsonfyForDart(payload);
|
2022-01-26 12:39:44 +08:00
|
|
|
payload.name = name;
|
2022-01-29 11:31:05 +08:00
|
|
|
events.push(payload);
|
2022-01-26 12:39:44 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
export function draw(frame) {
|
2022-01-28 04:30:38 +08:00
|
|
|
currentFrame = I420ToARGB(frame);
|
2022-01-26 12:39:44 +08:00
|
|
|
}
|
2022-01-20 02:27:49 +08:00
|
|
|
|
|
|
|
export function setConn(conn) {
|
2022-01-26 18:58:55 +08:00
|
|
|
window.curConn = conn;
|
2022-01-20 02:27:49 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
export function getConn() {
|
2022-01-26 18:58:55 +08:00
|
|
|
return window.curConn;
|
2022-01-20 12:49:57 +08:00
|
|
|
}
|
|
|
|
|
2022-01-27 01:30:29 +08:00
|
|
|
export async function startConn(id) {
|
|
|
|
try {
|
|
|
|
await curConn.start(id);
|
|
|
|
} catch (e) {
|
|
|
|
console.log(e);
|
|
|
|
msgbox('error', 'Error', String(e));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-20 18:02:20 +08:00
|
|
|
export function close() {
|
|
|
|
getConn()?.close();
|
|
|
|
setConn(undefined);
|
2022-01-26 12:39:44 +08:00
|
|
|
currentFrame = undefined;
|
2022-01-27 13:24:40 +08:00
|
|
|
events = undefined;
|
2022-01-20 18:02:20 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
export function newConn() {
|
2022-01-26 18:58:55 +08:00
|
|
|
window.curConn?.close();
|
2022-01-27 13:24:40 +08:00
|
|
|
events = [];
|
2022-01-20 12:49:57 +08:00
|
|
|
const conn = new Connection();
|
|
|
|
setConn(conn);
|
2022-01-20 18:02:20 +08:00
|
|
|
return conn;
|
2022-01-20 12:49:57 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
let sodium;
|
|
|
|
export async function verify(signed, pk) {
|
|
|
|
if (!sodium) {
|
|
|
|
await _sodium.ready;
|
|
|
|
sodium = _sodium;
|
|
|
|
}
|
2022-01-20 15:41:11 +08:00
|
|
|
if (typeof pk == 'string') {
|
|
|
|
pk = decodeBase64(pk);
|
|
|
|
}
|
2022-01-20 12:49:57 +08:00
|
|
|
return sodium.crypto_sign_open(signed, pk);
|
|
|
|
}
|
|
|
|
|
2022-01-20 15:41:11 +08:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2022-01-20 18:02:20 +08:00
|
|
|
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);
|
|
|
|
};
|
|
|
|
|
2022-01-20 15:41:11 +08:00
|
|
|
export function encrypt(unsigned, nonce, key) {
|
2022-01-20 18:02:20 +08:00
|
|
|
return sodium.crypto_secretbox_easy(unsigned, makeOnce(nonce), key);
|
2022-01-20 15:41:11 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
export function decrypt(signed, nonce, key) {
|
2022-01-20 18:02:20 +08:00
|
|
|
return sodium.crypto_secretbox_open_easy(signed, makeOnce(nonce), key);
|
2022-01-26 12:39:44 +08:00
|
|
|
}
|
|
|
|
|
2022-01-27 18:58:29 +08:00
|
|
|
export async function decompress(compressedArray) {
|
2022-01-26 12:39:44 +08:00
|
|
|
const MAX = 1024 * 1024 * 64;
|
|
|
|
const MIN = 1024 * 1024;
|
2022-01-27 13:24:40 +08:00
|
|
|
let n = 30 * compressedArray.length;
|
2022-01-26 12:39:44 +08:00
|
|
|
if (n > MAX) {
|
|
|
|
n = MAX;
|
|
|
|
}
|
|
|
|
if (n < MIN) {
|
|
|
|
n = MIN;
|
|
|
|
}
|
|
|
|
try {
|
2022-01-27 18:58:29 +08:00
|
|
|
if (!decompressor) {
|
|
|
|
await initZstd();
|
|
|
|
}
|
2022-01-26 12:39:44 +08:00
|
|
|
return decompressor.decode(compressedArray, n);
|
|
|
|
} catch (e) {
|
|
|
|
console.error('decompress failed: ' + e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
window.setByName = (name, value) => {
|
2022-01-26 18:58:55 +08:00
|
|
|
try {
|
|
|
|
value = JSON.parse(value);
|
2022-01-27 01:30:29 +08:00
|
|
|
} catch (e) { }
|
2022-01-26 12:39:44 +08:00
|
|
|
switch (name) {
|
2022-01-26 18:58:55 +08:00
|
|
|
case 'connect':
|
2022-01-26 12:39:44 +08:00
|
|
|
newConn();
|
2022-01-27 13:24:40 +08:00
|
|
|
startConn(String(value));
|
2022-01-26 12:39:44 +08:00
|
|
|
break;
|
|
|
|
case 'login':
|
2022-01-26 18:58:55 +08:00
|
|
|
curConn.login(value.password, value.remember || false);
|
2022-01-26 12:39:44 +08:00
|
|
|
break;
|
|
|
|
case 'close':
|
|
|
|
close();
|
|
|
|
break;
|
|
|
|
case 'refresh':
|
2022-01-26 18:58:55 +08:00
|
|
|
curConn.refresh();
|
2022-01-26 12:39:44 +08:00
|
|
|
break;
|
|
|
|
case 'reconnect':
|
2022-01-26 18:58:55 +08:00
|
|
|
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.ctrlAltDe();
|
|
|
|
break;
|
|
|
|
case 'switch_display':
|
|
|
|
curConn.switchDisplay(value);
|
|
|
|
break;
|
|
|
|
case 'remove':
|
|
|
|
const peers = JSON.parse(localStorage.getItem('peers') || '{}');
|
|
|
|
delete peers[value];
|
|
|
|
localStorage.setItem('peers', JSON.stringify(peers));
|
|
|
|
break;
|
|
|
|
case 'input_key':
|
|
|
|
curConn.inputKey(value.name, value.alt || false, value.ctrl || false, value.shift || false, value.command || false);
|
|
|
|
break;
|
|
|
|
case 'input_string':
|
|
|
|
curConn.inputString(value);
|
|
|
|
break;
|
|
|
|
case 'send_mouse':
|
|
|
|
let mask = 0;
|
|
|
|
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, value.x || 0, value.y || 0, value.alt || false, value.ctrl || false, value.shift || false, value.command || false);
|
|
|
|
break;
|
|
|
|
case 'option':
|
|
|
|
localStorage.setItem(value.name, value.value);
|
|
|
|
break;
|
|
|
|
case 'peer_option':
|
2022-01-29 11:31:05 +08:00
|
|
|
curConn.setOption(value.name, value.value);
|
2022-01-26 18:58:55 +08:00
|
|
|
break;
|
|
|
|
case 'input_os_password':
|
|
|
|
curConn.inputOsPassword(value, true);
|
2022-01-26 12:39:44 +08:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-26 18:58:55 +08:00
|
|
|
window.getByName = (name, arg) => {
|
|
|
|
try {
|
|
|
|
arg = JSON.parse(arg);
|
2022-01-27 01:30:29 +08:00
|
|
|
} catch (e) { }
|
2022-01-26 12:39:44 +08:00
|
|
|
switch (name) {
|
|
|
|
case 'peers':
|
2022-01-27 01:30:29 +08:00
|
|
|
return localStorage.getItem('peers') || '[]';
|
2022-01-26 12:39:44 +08:00
|
|
|
break;
|
2022-01-26 18:58:55 +08:00
|
|
|
case 'remote_id':
|
|
|
|
return localStorage.getItem('remote-id') || '';
|
|
|
|
break;
|
|
|
|
case 'remember':
|
|
|
|
return curConn.getRemember();
|
|
|
|
break;
|
2022-01-26 12:39:44 +08:00
|
|
|
case 'event':
|
2022-01-27 13:24:40 +08:00
|
|
|
if (events && events.length) {
|
2022-01-26 12:39:44 +08:00
|
|
|
const e = events[0];
|
|
|
|
events.splice(0, 1);
|
2022-01-26 18:58:55 +08:00
|
|
|
return JSON.stringify(e);
|
2022-01-26 12:39:44 +08:00
|
|
|
}
|
|
|
|
break;
|
2022-01-26 18:58:55 +08:00
|
|
|
case 'toggle_option':
|
|
|
|
return curConn.getOption(arg);
|
|
|
|
break;
|
|
|
|
case 'option':
|
|
|
|
return localStorage.getItem(arg);
|
|
|
|
break;
|
|
|
|
case 'image_quality':
|
|
|
|
return curConn.getImageQuality();
|
|
|
|
break;
|
|
|
|
case 'translate':
|
|
|
|
return arg.text;
|
|
|
|
break;
|
|
|
|
case 'peer_option':
|
|
|
|
return curConn.getOption(arg);
|
|
|
|
break;
|
2022-01-27 01:30:29 +08:00
|
|
|
case 'test_if_valid_server':
|
|
|
|
break;
|
2022-01-26 12:39:44 +08:00
|
|
|
}
|
2022-01-27 01:30:29 +08:00
|
|
|
return '';
|
2022-01-26 18:58:55 +08:00
|
|
|
}
|
|
|
|
|
2022-01-27 18:58:29 +08:00
|
|
|
window.init = async () => {
|
|
|
|
await initZstd();
|
|
|
|
}
|
|
|
|
|
2022-01-28 03:53:23 +08:00
|
|
|
let yPtr, yPtrLen, uPtr, uPtrLen, vPtr, vPtrLen, outPtr, outPtrLen;
|
|
|
|
// let testSpeed = [0, 0];
|
2022-01-28 04:30:38 +08:00
|
|
|
export function I420ToARGB(yb) {
|
2022-01-28 03:53:23 +08:00
|
|
|
if (!wasmExports) return;
|
|
|
|
// testSpeed[0] += 1;
|
|
|
|
const tm0 = new Date().getTime();
|
|
|
|
const { malloc, free, memory } = wasmExports;
|
|
|
|
const HEAPU8 = new Uint8Array(memory.buffer);
|
|
|
|
let n = yb.y.bytes.length;
|
|
|
|
if (yPtrLen != n) {
|
|
|
|
if (yPtr) free(yPtr);
|
|
|
|
yPtrLen = n;
|
|
|
|
yPtr = malloc(n);
|
|
|
|
}
|
|
|
|
HEAPU8.set(yb.y.bytes, yPtr);
|
|
|
|
n = yb.u.bytes.length;
|
|
|
|
if (uPtrLen != n) {
|
|
|
|
if (uPtr) free(uPtr);
|
|
|
|
uPtrLen = n;
|
|
|
|
uPtr = malloc(n);
|
|
|
|
}
|
|
|
|
HEAPU8.set(yb.u.bytes, uPtr);
|
|
|
|
n = yb.v.bytes.length;
|
|
|
|
if (vPtrLen != n) {
|
|
|
|
if (vPtr) free(vPtr);
|
|
|
|
vPtrLen = n;
|
|
|
|
vPtr = malloc(n);
|
|
|
|
}
|
|
|
|
HEAPU8.set(yb.v.bytes, vPtr);
|
2022-01-29 20:45:53 +08:00
|
|
|
const w = yb.format.displayWidth;
|
|
|
|
const h = yb.format.displayHeight;
|
2022-01-28 03:53:23 +08:00
|
|
|
n = w * h * 4;
|
|
|
|
if (outPtrLen != n) {
|
|
|
|
if (outPtr) free(outPtr);
|
|
|
|
outPtrLen = n;
|
|
|
|
outPtr = malloc(n);
|
2022-01-29 20:45:53 +08:00
|
|
|
HEAPU8.fill(255, outPtr, outPtr + n);
|
2022-01-28 03:53:23 +08:00
|
|
|
}
|
|
|
|
// const res = wasmExports.I420ToARGB(yPtr, yb.y.stride, uPtr, yb.u.stride, vPtr, yb.v.stride, outPtr, w * 4, w, h);
|
2022-01-29 20:45:53 +08:00
|
|
|
// const res = wasmExports.AVX_YUV_to_ARGB(outPtr, yPtr, yb.y.stride, uPtr, yb.u.stride, vPtr, yb.v.stride, w, h);
|
|
|
|
const res = wasmExports.yuv420_rgb24_std(w, h, yPtr, uPtr, vPtr, yb.y.stride, yb.v.stride, outPtr, w * 4, 1);
|
2022-01-28 03:53:23 +08:00
|
|
|
const out = HEAPU8.slice(outPtr, outPtr + n);
|
|
|
|
/*
|
|
|
|
testSpeed[1] += new Date().getTime() - tm0;
|
|
|
|
if (testSpeed[0] > 30) {
|
|
|
|
console.log(testSpeed[1] / testSpeed[0]);
|
|
|
|
testSpeed = [0, 0];
|
|
|
|
}
|
|
|
|
*/
|
2022-01-27 23:32:51 +08:00
|
|
|
return out;
|
2022-01-27 18:58:29 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
async function initZstd() {
|
|
|
|
loadOpus(() => { });
|
|
|
|
loadVp9(() => { });
|
2022-01-28 03:53:23 +08:00
|
|
|
const response = await fetch('yuv.wasm');
|
|
|
|
const file = await response.arrayBuffer();
|
|
|
|
const wasm = await WebAssembly.instantiate(file);
|
|
|
|
wasmExports = wasm.instance.exports;
|
|
|
|
console.log('yuv ready');
|
2022-01-27 18:58:29 +08:00
|
|
|
const tmp = new zstd.ZSTDDecoder();
|
|
|
|
await tmp.init();
|
|
|
|
console.log('zstd ready');
|
|
|
|
decompressor = tmp;
|
2022-01-28 03:53:23 +08:00
|
|
|
}
|