connection.ts 22 KB

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