connection.ts 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775
  1. import Websock from "./websock";
  2. import * as message from "./message.js";
  3. import * as rendezvous from "./rendezvous.js";
  4. import { loadVp9 } from "./codec";
  5. import * as sha256 from "fast-sha256";
  6. import * as globals from "./globals";
  7. import { decompress, mapKey, sleep } from "./common";
  8. const PORT = 21116;
  9. // only the first is used to init `HOST`
  10. const HOSTS = [
  11. "rs-sg.rustdesk.com",
  12. "rs-cn.rustdesk.com",
  13. "rs-us.rustdesk.com",
  14. ];
  15. let HOST = localStorage.getItem("rendezvous-server") || HOSTS[0];
  16. const SCHEMA = "ws://";
  17. type MsgboxCallback = (type: string, title: string, text: string) => void;
  18. type DrawCallback = (data: Uint8Array) => void;
  19. //const cursorCanvas = document.createElement("canvas");
  20. export default class Connection {
  21. _msgs: any[];
  22. _ws: Websock | undefined;
  23. _interval: any;
  24. _id: string;
  25. _hash: message.Hash | undefined;
  26. _msgbox: MsgboxCallback;
  27. _draw: DrawCallback;
  28. _peerInfo: message.PeerInfo | undefined;
  29. _firstFrame: Boolean | undefined;
  30. _videoDecoder: any;
  31. _password: Uint8Array | undefined;
  32. _options: any;
  33. _videoTestSpeed: number[];
  34. //_cursors: { [name: number]: any };
  35. constructor() {
  36. this._msgbox = globals.msgbox;
  37. this._draw = globals.draw;
  38. this._msgs = [];
  39. this._id = "";
  40. this._videoTestSpeed = [0, 0];
  41. //this._cursors = {};
  42. }
  43. async start(id: string) {
  44. try {
  45. await this._start(id);
  46. } catch (e: any) {
  47. this.msgbox(
  48. "error",
  49. "Connection Error",
  50. e.type == "close" ? "Reset by the peer" : String(e)
  51. );
  52. }
  53. }
  54. async _start(id: string) {
  55. if (!this._options) {
  56. this._options = globals.getPeers()[id] || {};
  57. }
  58. if (!this._password) {
  59. const p = this.getOption("password");
  60. if (p) {
  61. try {
  62. this._password = Uint8Array.from(JSON.parse("[" + p + "]"));
  63. } catch (e) {
  64. console.error(e);
  65. }
  66. }
  67. }
  68. this._interval = setInterval(() => {
  69. while (this._msgs.length) {
  70. this._ws?.sendMessage(this._msgs[0]);
  71. this._msgs.splice(0, 1);
  72. }
  73. }, 1);
  74. this.loadVideoDecoder();
  75. const uri = getDefaultUri();
  76. const ws = new Websock(uri, true);
  77. this._ws = ws;
  78. this._id = id;
  79. console.log(
  80. new Date() + ": Conntecting to rendezvoous server: " + uri + ", for " + id
  81. );
  82. await ws.open();
  83. console.log(new Date() + ": Connected to rendezvoous server");
  84. const conn_type = rendezvous.ConnType.DEFAULT_CONN;
  85. const nat_type = rendezvous.NatType.SYMMETRIC;
  86. const punch_hole_request = rendezvous.PunchHoleRequest.fromPartial({
  87. id,
  88. licence_key: localStorage.getItem("key") || undefined,
  89. conn_type,
  90. nat_type,
  91. token: localStorage.getItem("access_token") || undefined,
  92. });
  93. ws.sendRendezvous({ punch_hole_request });
  94. const msg = (await ws.next()) as rendezvous.RendezvousMessage;
  95. ws.close();
  96. console.log(new Date() + ": Got relay response");
  97. const phr = msg.punch_hole_response;
  98. const rr = msg.relay_response;
  99. if (phr) {
  100. if (phr?.other_failure) {
  101. this.msgbox("error", "Error", phr?.other_failure);
  102. return;
  103. }
  104. if (phr.failure != rendezvous.PunchHoleResponse_Failure.UNRECOGNIZED) {
  105. switch (phr?.failure) {
  106. case rendezvous.PunchHoleResponse_Failure.ID_NOT_EXIST:
  107. this.msgbox("error", "Error", "ID does not exist");
  108. break;
  109. case rendezvous.PunchHoleResponse_Failure.OFFLINE:
  110. this.msgbox("error", "Error", "Remote desktop is offline");
  111. break;
  112. case rendezvous.PunchHoleResponse_Failure.LICENSE_MISMATCH:
  113. this.msgbox("error", "Error", "Key mismatch");
  114. break;
  115. case rendezvous.PunchHoleResponse_Failure.LICENSE_OVERUSE:
  116. this.msgbox("error", "Error", "Key overuse");
  117. break;
  118. }
  119. }
  120. } else if (rr) {
  121. if (!rr.version) {
  122. this.msgbox("error", "Error", "Remote version is low, not support web");
  123. return;
  124. }
  125. await this.connectRelay(rr);
  126. }
  127. }
  128. async connectRelay(rr: rendezvous.RelayResponse) {
  129. const pk = rr.pk;
  130. let uri = rr.relay_server;
  131. if (uri) {
  132. uri = getrUriFromRs(uri, true, 2);
  133. } else {
  134. uri = getDefaultUri(true);
  135. }
  136. const uuid = rr.uuid;
  137. console.log(new Date() + ": Connecting to relay server: " + uri);
  138. const ws = new Websock(uri, false);
  139. await ws.open();
  140. console.log(new Date() + ": Connected to relay server");
  141. this._ws = ws;
  142. const request_relay = rendezvous.RequestRelay.fromPartial({
  143. licence_key: localStorage.getItem("key") || undefined,
  144. uuid,
  145. });
  146. ws.sendRendezvous({ request_relay });
  147. const secure = (await this.secure(pk)) || false;
  148. globals.pushEvent("connection_ready", { secure, direct: false });
  149. await this.msgLoop();
  150. }
  151. async secure(pk: Uint8Array | undefined) {
  152. if (pk) {
  153. const RS_PK = "OeVuKk5nlHiXp+APNn0Y3pC1Iwpwn44JGqrQCsWqmBw=";
  154. try {
  155. pk = await globals.verify(pk, localStorage.getItem("key") || RS_PK);
  156. if (pk) {
  157. const idpk = message.IdPk.decode(pk);
  158. if (idpk.id == this._id) {
  159. pk = idpk.pk;
  160. }
  161. }
  162. if (pk?.length != 32) {
  163. pk = undefined;
  164. }
  165. } catch (e) {
  166. console.error(e);
  167. pk = undefined;
  168. }
  169. if (!pk)
  170. console.error(
  171. "Handshake failed: invalid public key from rendezvous server"
  172. );
  173. }
  174. if (!pk) {
  175. // send an empty message out in case server is setting up secure and waiting for first message
  176. const public_key = message.PublicKey.fromPartial({});
  177. this._ws?.sendMessage({ public_key });
  178. return;
  179. }
  180. const msg = (await this._ws?.next()) as message.Message;
  181. let signedId: any = msg?.signed_id;
  182. if (!signedId) {
  183. console.error("Handshake failed: invalid message type");
  184. const public_key = message.PublicKey.fromPartial({});
  185. this._ws?.sendMessage({ public_key });
  186. return;
  187. }
  188. try {
  189. signedId = await globals.verify(signedId.id, Uint8Array.from(pk!));
  190. } catch (e) {
  191. console.error(e);
  192. // fall back to non-secure connection in case pk mismatch
  193. console.error("pk mismatch, fall back to non-secure");
  194. const public_key = message.PublicKey.fromPartial({});
  195. this._ws?.sendMessage({ public_key });
  196. return;
  197. }
  198. const idpk = message.IdPk.decode(signedId);
  199. const id = idpk.id;
  200. const theirPk = idpk.pk;
  201. if (id != this._id!) {
  202. console.error("Handshake failed: sign failure");
  203. const public_key = message.PublicKey.fromPartial({});
  204. this._ws?.sendMessage({ public_key });
  205. return;
  206. }
  207. if (theirPk.length != 32) {
  208. console.error(
  209. "Handshake failed: invalid public box key length from peer"
  210. );
  211. const public_key = message.PublicKey.fromPartial({});
  212. this._ws?.sendMessage({ public_key });
  213. return;
  214. }
  215. const [mySk, asymmetric_value] = globals.genBoxKeyPair();
  216. const secret_key = globals.genSecretKey();
  217. const symmetric_value = globals.seal(secret_key, theirPk, mySk);
  218. const public_key = message.PublicKey.fromPartial({
  219. asymmetric_value,
  220. symmetric_value,
  221. });
  222. this._ws?.sendMessage({ public_key });
  223. this._ws?.setSecretKey(secret_key);
  224. console.log("secured");
  225. return true;
  226. }
  227. async msgLoop() {
  228. while (true) {
  229. const msg = (await this._ws?.next()) as message.Message;
  230. if (msg?.hash) {
  231. this._hash = msg?.hash;
  232. if (!this._password)
  233. this.msgbox("input-password", "Password Required", "");
  234. this.login();
  235. } else if (msg?.test_delay) {
  236. const test_delay = msg?.test_delay;
  237. console.log(test_delay);
  238. if (!test_delay.from_client) {
  239. this._ws?.sendMessage({ test_delay });
  240. }
  241. } else if (msg?.login_response) {
  242. const r = msg?.login_response;
  243. if (r.error) {
  244. if (r.error == "Wrong Password") {
  245. this._password = undefined;
  246. this.msgbox(
  247. "re-input-password",
  248. r.error,
  249. "Do you want to enter again?"
  250. );
  251. } else {
  252. this.msgbox("error", "Login Error", r.error);
  253. }
  254. } else if (r.peer_info) {
  255. this.handlePeerInfo(r.peer_info);
  256. }
  257. } else if (msg?.video_frame) {
  258. this.handleVideoFrame(msg?.video_frame!);
  259. } else if (msg?.clipboard) {
  260. const cb = msg?.clipboard;
  261. if (cb.compress) {
  262. const c = await decompress(cb.content);
  263. if (!c) continue;
  264. cb.content = c;
  265. }
  266. try {
  267. globals.copyToClipboard(new TextDecoder().decode(cb.content));
  268. } catch (e) {
  269. console.error(e);
  270. }
  271. // globals.pushEvent("clipboard", cb);
  272. } else if (msg?.cursor_data) {
  273. const cd = msg?.cursor_data;
  274. const c = await decompress(cd.colors);
  275. if (!c) continue;
  276. cd.colors = c;
  277. globals.pushEvent("cursor_data", cd);
  278. /*
  279. let ctx = cursorCanvas.getContext("2d");
  280. cursorCanvas.width = cd.width;
  281. cursorCanvas.height = cd.height;
  282. let imgData = new ImageData(
  283. new Uint8ClampedArray(c),
  284. cd.width,
  285. cd.height
  286. );
  287. ctx?.clearRect(0, 0, cd.width, cd.height);
  288. ctx?.putImageData(imgData, 0, 0);
  289. let url = cursorCanvas.toDataURL();
  290. const img = document.createElement("img");
  291. img.src = url;
  292. this._cursors[cd.id] = img;
  293. //cursorCanvas.width /= 2.;
  294. //cursorCanvas.height /= 2.;
  295. //ctx?.drawImage(img, cursorCanvas.width, cursorCanvas.height);
  296. url = cursorCanvas.toDataURL();
  297. document.body.style.cursor =
  298. "url(" + url + ")" + cd.hotx + " " + cd.hoty + ", default";
  299. console.log(document.body.style.cursor);
  300. */
  301. } else if (msg?.cursor_id) {
  302. globals.pushEvent("cursor_id", { id: msg?.cursor_id });
  303. } else if (msg?.cursor_position) {
  304. globals.pushEvent("cursor_position", msg?.cursor_position);
  305. } else if (msg?.misc) {
  306. if (!this.handleMisc(msg?.misc)) break;
  307. } else if (msg?.audio_frame) {
  308. globals.playAudio(msg?.audio_frame.data);
  309. }
  310. }
  311. }
  312. msgbox(type_: string, title: string, text: string) {
  313. this._msgbox?.(type_, title, text);
  314. }
  315. draw(frame: any) {
  316. this._draw?.(frame);
  317. globals.draw(frame);
  318. }
  319. close() {
  320. this._msgs = [];
  321. clearInterval(this._interval);
  322. this._ws?.close();
  323. this._videoDecoder?.close();
  324. }
  325. refresh() {
  326. const misc = message.Misc.fromPartial({ refresh_video: true });
  327. this._ws?.sendMessage({ misc });
  328. }
  329. setMsgbox(callback: MsgboxCallback) {
  330. this._msgbox = callback;
  331. }
  332. setDraw(callback: DrawCallback) {
  333. this._draw = callback;
  334. }
  335. login(password: string | undefined = undefined) {
  336. if (password) {
  337. const salt = this._hash?.salt;
  338. let p = hash([password, salt!]);
  339. this._password = p;
  340. const challenge = this._hash?.challenge;
  341. p = hash([p, challenge!]);
  342. this.msgbox("connecting", "Connecting...", "Logging in...");
  343. this._sendLoginMessage(p);
  344. } else {
  345. let p = this._password;
  346. if (p) {
  347. const challenge = this._hash?.challenge;
  348. p = hash([p, challenge!]);
  349. }
  350. this._sendLoginMessage(p);
  351. }
  352. }
  353. async reconnect() {
  354. this.close();
  355. await this.start(this._id);
  356. }
  357. _sendLoginMessage(password: Uint8Array | undefined = undefined) {
  358. const login_request = message.LoginRequest.fromPartial({
  359. username: this._id!,
  360. my_id: "web", // to-do
  361. my_name: "web", // to-do
  362. password,
  363. option: this.getOptionMessage(),
  364. video_ack_required: true,
  365. });
  366. this._ws?.sendMessage({ login_request });
  367. }
  368. getOptionMessage(): message.OptionMessage | undefined {
  369. let n = 0;
  370. const msg = message.OptionMessage.fromPartial({});
  371. const q = this.getImageQualityEnum(this.getImageQuality(), true);
  372. const yes = message.OptionMessage_BoolOption.Yes;
  373. if (q != undefined) {
  374. msg.image_quality = q;
  375. n += 1;
  376. }
  377. if (this._options["show-remote-cursor"]) {
  378. msg.show_remote_cursor = yes;
  379. n += 1;
  380. }
  381. if (this._options["lock-after-session-end"]) {
  382. msg.lock_after_session_end = yes;
  383. n += 1;
  384. }
  385. if (this._options["privacy-mode"]) {
  386. msg.privacy_mode = yes;
  387. n += 1;
  388. }
  389. if (this._options["disable-audio"]) {
  390. msg.disable_audio = yes;
  391. n += 1;
  392. }
  393. if (this._options["disable-clipboard"]) {
  394. msg.disable_clipboard = yes;
  395. n += 1;
  396. }
  397. return n > 0 ? msg : undefined;
  398. }
  399. sendVideoReceived() {
  400. const misc = message.Misc.fromPartial({ video_received: true });
  401. this._ws?.sendMessage({ misc });
  402. }
  403. handleVideoFrame(vf: message.VideoFrame) {
  404. if (!this._firstFrame) {
  405. this.msgbox("", "", "");
  406. this._firstFrame = true;
  407. }
  408. if (vf.vp9s) {
  409. const dec = this._videoDecoder;
  410. var tm = new Date().getTime();
  411. var i = 0;
  412. const n = vf.vp9s?.frames.length;
  413. vf.vp9s.frames.forEach((f) => {
  414. dec.processFrame(f.data.slice(0).buffer, (ok: any) => {
  415. i++;
  416. if (i == n) this.sendVideoReceived();
  417. if (ok && dec.frameBuffer && n == i) {
  418. this.draw(dec.frameBuffer);
  419. const now = new Date().getTime();
  420. var elapsed = now - tm;
  421. this._videoTestSpeed[1] += elapsed;
  422. this._videoTestSpeed[0] += 1;
  423. if (this._videoTestSpeed[0] >= 30) {
  424. console.log(
  425. "video decoder: " +
  426. parseInt(
  427. "" + this._videoTestSpeed[1] / this._videoTestSpeed[0]
  428. )
  429. );
  430. this._videoTestSpeed = [0, 0];
  431. }
  432. }
  433. });
  434. });
  435. }
  436. }
  437. handlePeerInfo(pi: message.PeerInfo) {
  438. this._peerInfo = pi;
  439. if (pi.displays.length == 0) {
  440. this.msgbox("error", "Remote Error", "No Display");
  441. return;
  442. }
  443. this.msgbox("success", "Successful", "Connected, waiting for image...");
  444. globals.pushEvent("peer_info", pi);
  445. const p = this.shouldAutoLogin();
  446. if (p) this.inputOsPassword(p);
  447. const username = this.getOption("info")?.username;
  448. if (username && !pi.username) pi.username = username;
  449. this.setOption("info", pi);
  450. if (this.getRemember()) {
  451. if (this._password?.length) {
  452. const p = this._password.toString();
  453. if (p != this.getOption("password")) {
  454. this.setOption("password", p);
  455. console.log("remember password of " + this._id);
  456. }
  457. }
  458. } else {
  459. this.setOption("password", undefined);
  460. }
  461. }
  462. shouldAutoLogin(): string {
  463. const l = this.getOption("lock-after-session-end");
  464. const a = !!this.getOption("auto-login");
  465. const p = this.getOption("os-password");
  466. if (p && l && a) {
  467. return p;
  468. }
  469. return "";
  470. }
  471. handleMisc(misc: message.Misc) {
  472. if (misc.audio_format) {
  473. globals.initAudio(
  474. misc.audio_format.channels,
  475. misc.audio_format.sample_rate
  476. );
  477. } else if (misc.chat_message) {
  478. globals.pushEvent("chat", { text: misc.chat_message.text });
  479. } else if (misc.permission_info) {
  480. const p = misc.permission_info;
  481. console.info("Change permission " + p.permission + " -> " + p.enabled);
  482. let name;
  483. switch (p.permission) {
  484. case message.PermissionInfo_Permission.Keyboard:
  485. name = "keyboard";
  486. break;
  487. case message.PermissionInfo_Permission.Clipboard:
  488. name = "clipboard";
  489. break;
  490. case message.PermissionInfo_Permission.Audio:
  491. name = "audio";
  492. break;
  493. default:
  494. return;
  495. }
  496. globals.pushEvent("permission", { [name]: p.enabled });
  497. } else if (misc.switch_display) {
  498. this.loadVideoDecoder();
  499. globals.pushEvent("switch_display", misc.switch_display);
  500. } else if (misc.close_reason) {
  501. this.msgbox("error", "Connection Error", misc.close_reason);
  502. this.close();
  503. return false;
  504. }
  505. return true;
  506. }
  507. getRemember(): Boolean {
  508. return this._options["remember"] || false;
  509. }
  510. setRemember(v: Boolean) {
  511. this.setOption("remember", v);
  512. }
  513. getOption(name: string): any {
  514. return this._options[name];
  515. }
  516. setOption(name: string, value: any) {
  517. if (value == undefined) {
  518. delete this._options[name];
  519. } else {
  520. this._options[name] = value;
  521. }
  522. this._options["tm"] = new Date().getTime();
  523. const peers = globals.getPeers();
  524. peers[this._id] = this._options;
  525. localStorage.setItem("peers", JSON.stringify(peers));
  526. }
  527. inputKey(
  528. name: string,
  529. down: boolean,
  530. press: boolean,
  531. alt: Boolean,
  532. ctrl: Boolean,
  533. shift: Boolean,
  534. command: Boolean
  535. ) {
  536. const key_event = mapKey(name, globals.isDesktop());
  537. if (!key_event) return;
  538. if (alt && (name == "VK_MENU" || name == "RAlt")) {
  539. alt = false;
  540. }
  541. if (ctrl && (name == "VK_CONTROL" || name == "RControl")) {
  542. ctrl = false;
  543. }
  544. if (shift && (name == "VK_SHIFT" || name == "RShift")) {
  545. shift = false;
  546. }
  547. if (command && (name == "Meta" || name == "RWin")) {
  548. command = false;
  549. }
  550. key_event.down = down;
  551. key_event.press = press;
  552. key_event.modifiers = this.getMod(alt, ctrl, shift, command);
  553. this._ws?.sendMessage({ key_event });
  554. }
  555. ctrlAltDel() {
  556. const key_event = message.KeyEvent.fromPartial({ down: true });
  557. if (this._peerInfo?.platform == "Windows") {
  558. key_event.control_key = message.ControlKey.CtrlAltDel;
  559. } else {
  560. key_event.control_key = message.ControlKey.Delete;
  561. key_event.modifiers = this.getMod(true, true, false, false);
  562. }
  563. this._ws?.sendMessage({ key_event });
  564. }
  565. inputString(seq: string) {
  566. const key_event = message.KeyEvent.fromPartial({ seq });
  567. this._ws?.sendMessage({ key_event });
  568. }
  569. switchDisplay(display: number) {
  570. const switch_display = message.SwitchDisplay.fromPartial({ display });
  571. const misc = message.Misc.fromPartial({ switch_display });
  572. this._ws?.sendMessage({ misc });
  573. }
  574. async inputOsPassword(seq: string) {
  575. this.inputMouse();
  576. await sleep(50);
  577. this.inputMouse(0, 3, 3);
  578. await sleep(50);
  579. this.inputMouse(1 | (1 << 3));
  580. this.inputMouse(2 | (1 << 3));
  581. await sleep(1200);
  582. const key_event = message.KeyEvent.fromPartial({ press: true, seq });
  583. this._ws?.sendMessage({ key_event });
  584. }
  585. lockScreen() {
  586. const key_event = message.KeyEvent.fromPartial({
  587. down: true,
  588. control_key: message.ControlKey.LockScreen,
  589. });
  590. this._ws?.sendMessage({ key_event });
  591. }
  592. getMod(alt: Boolean, ctrl: Boolean, shift: Boolean, command: Boolean) {
  593. const mod: message.ControlKey[] = [];
  594. if (alt) mod.push(message.ControlKey.Alt);
  595. if (ctrl) mod.push(message.ControlKey.Control);
  596. if (shift) mod.push(message.ControlKey.Shift);
  597. if (command) mod.push(message.ControlKey.Meta);
  598. return mod;
  599. }
  600. inputMouse(
  601. mask: number = 0,
  602. x: number = 0,
  603. y: number = 0,
  604. alt: Boolean = false,
  605. ctrl: Boolean = false,
  606. shift: Boolean = false,
  607. command: Boolean = false
  608. ) {
  609. const mouse_event = message.MouseEvent.fromPartial({
  610. mask,
  611. x,
  612. y,
  613. modifiers: this.getMod(alt, ctrl, shift, command),
  614. });
  615. this._ws?.sendMessage({ mouse_event });
  616. }
  617. toggleOption(name: string) {
  618. const v = !this._options[name];
  619. const option = message.OptionMessage.fromPartial({});
  620. const v2 = v
  621. ? message.OptionMessage_BoolOption.Yes
  622. : message.OptionMessage_BoolOption.No;
  623. switch (name) {
  624. case "show-remote-cursor":
  625. option.show_remote_cursor = v2;
  626. break;
  627. case "disable-audio":
  628. option.disable_audio = v2;
  629. break;
  630. case "disable-clipboard":
  631. option.disable_clipboard = v2;
  632. break;
  633. case "lock-after-session-end":
  634. option.lock_after_session_end = v2;
  635. break;
  636. case "privacy-mode":
  637. option.privacy_mode = v2;
  638. break;
  639. case "block-input":
  640. option.block_input = message.OptionMessage_BoolOption.Yes;
  641. break;
  642. case "unblock-input":
  643. option.block_input = message.OptionMessage_BoolOption.No;
  644. break;
  645. default:
  646. return;
  647. }
  648. if (name.indexOf("block-input") < 0) this.setOption(name, v);
  649. const misc = message.Misc.fromPartial({ option });
  650. this._ws?.sendMessage({ misc });
  651. }
  652. getImageQuality() {
  653. return this.getOption("image-quality");
  654. }
  655. getImageQualityEnum(
  656. value: string,
  657. ignoreDefault: Boolean
  658. ): message.ImageQuality | undefined {
  659. switch (value) {
  660. case "low":
  661. return message.ImageQuality.Low;
  662. case "best":
  663. return message.ImageQuality.Best;
  664. case "balanced":
  665. return ignoreDefault ? undefined : message.ImageQuality.Balanced;
  666. default:
  667. return undefined;
  668. }
  669. }
  670. setImageQuality(value: string) {
  671. this.setOption("image-quality", value);
  672. const image_quality = this.getImageQualityEnum(value, false);
  673. if (image_quality == undefined) return;
  674. const option = message.OptionMessage.fromPartial({ image_quality });
  675. const misc = message.Misc.fromPartial({ option });
  676. this._ws?.sendMessage({ misc });
  677. }
  678. loadVideoDecoder() {
  679. this._videoDecoder?.close();
  680. loadVp9((decoder: any) => {
  681. this._videoDecoder = decoder;
  682. console.log("vp9 loaded");
  683. console.log(decoder);
  684. });
  685. }
  686. }
  687. function testDelay() {
  688. var nearest = "";
  689. HOSTS.forEach((host) => {
  690. const now = new Date().getTime();
  691. new Websock(getrUriFromRs(host), true).open().then(() => {
  692. console.log("latency of " + host + ": " + (new Date().getTime() - now));
  693. if (!nearest) {
  694. HOST = host;
  695. localStorage.setItem("rendezvous-server", host);
  696. }
  697. });
  698. });
  699. }
  700. testDelay();
  701. function getDefaultUri(isRelay: Boolean = false): string {
  702. const host = localStorage.getItem("custom-rendezvous-server");
  703. return getrUriFromRs(host || HOST, isRelay);
  704. }
  705. function getrUriFromRs(
  706. uri: string,
  707. isRelay: Boolean = false,
  708. roffset: number = 0
  709. ): string {
  710. if (uri.indexOf(":") > 0) {
  711. const tmp = uri.split(":");
  712. const port = parseInt(tmp[1]);
  713. uri = tmp[0] + ":" + (port + (isRelay ? roffset || 3 : 2));
  714. } else {
  715. uri += ":" + (PORT + (isRelay ? 3 : 2));
  716. }
  717. return SCHEMA + uri;
  718. }
  719. function hash(datas: (string | Uint8Array)[]): Uint8Array {
  720. const hasher = new sha256.Hash();
  721. datas.forEach((data) => {
  722. if (typeof data == "string") {
  723. data = new TextEncoder().encode(data);
  724. }
  725. return hasher.update(data);
  726. });
  727. return hasher.digest();
  728. }