@novnc/novnc 1.2.0 → 1.3.0-g65d6357

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/LICENSE.txt +0 -6
  2. package/README.md +9 -4
  3. package/core/decoders/copyrect.js +5 -0
  4. package/core/decoders/hextile.js +57 -3
  5. package/core/decoders/raw.js +12 -2
  6. package/core/decoders/tight.js +24 -8
  7. package/core/decoders/zrle.js +185 -0
  8. package/core/display.js +9 -151
  9. package/core/encodings.js +2 -0
  10. package/core/input/domkeytable.js +25 -21
  11. package/core/input/keyboard.js +12 -127
  12. package/core/input/util.js +18 -35
  13. package/core/input/vkeys.js +0 -1
  14. package/core/input/xtscancodes.js +5 -3
  15. package/core/rfb.js +119 -109
  16. package/core/util/browser.js +0 -17
  17. package/core/util/cursor.js +1 -11
  18. package/core/util/events.js +0 -4
  19. package/core/websock.js +76 -17
  20. package/docs/API.md +10 -3
  21. package/docs/LIBRARY.md +3 -7
  22. package/lib/base64.js +4 -4
  23. package/lib/decoders/copyrect.js +7 -2
  24. package/lib/decoders/hextile.js +63 -7
  25. package/lib/decoders/raw.js +13 -4
  26. package/lib/decoders/rre.js +2 -2
  27. package/lib/decoders/tight.js +38 -20
  28. package/lib/decoders/tightpng.js +8 -8
  29. package/lib/decoders/zrle.js +234 -0
  30. package/lib/deflator.js +4 -4
  31. package/lib/des.js +2 -2
  32. package/lib/display.js +45 -212
  33. package/lib/encodings.js +4 -0
  34. package/lib/inflator.js +4 -4
  35. package/lib/input/domkeytable.js +197 -194
  36. package/lib/input/fixedkeys.js +2 -2
  37. package/lib/input/gesturehandler.js +2 -2
  38. package/lib/input/keyboard.js +38 -158
  39. package/lib/input/keysym.js +2 -2
  40. package/lib/input/keysymdef.js +2 -2
  41. package/lib/input/util.js +34 -79
  42. package/lib/input/vkeys.js +2 -4
  43. package/lib/input/xtscancodes.js +11 -5
  44. package/lib/rfb.js +295 -285
  45. package/lib/util/browser.js +8 -26
  46. package/lib/util/cursor.js +4 -16
  47. package/lib/util/events.js +3 -5
  48. package/lib/util/eventtarget.js +3 -3
  49. package/lib/util/int.js +1 -1
  50. package/lib/util/logging.js +2 -2
  51. package/lib/vendor/pako/lib/utils/common.js +2 -2
  52. package/lib/vendor/pako/lib/zlib/adler32.js +1 -1
  53. package/lib/vendor/pako/lib/zlib/constants.js +2 -2
  54. package/lib/vendor/pako/lib/zlib/crc32.js +1 -1
  55. package/lib/vendor/pako/lib/zlib/deflate.js +113 -112
  56. package/lib/vendor/pako/lib/zlib/gzheader.js +1 -1
  57. package/lib/vendor/pako/lib/zlib/inffast.js +5 -5
  58. package/lib/vendor/pako/lib/zlib/inflate.js +50 -48
  59. package/lib/vendor/pako/lib/zlib/inftrees.js +3 -3
  60. package/lib/vendor/pako/lib/zlib/messages.js +2 -2
  61. package/lib/vendor/pako/lib/zlib/trees.js +4 -4
  62. package/lib/vendor/pako/lib/zlib/zstream.js +1 -1
  63. package/lib/websock.js +105 -44
  64. package/package.json +2 -7
  65. package/core/util/polyfill.js +0 -61
  66. package/lib/util/polyfill.js +0 -72
  67. package/lib/vendor/promise.js +0 -255
package/core/rfb.js CHANGED
@@ -25,7 +25,6 @@ import DES from "./des.js";
25
25
  import KeyTable from "./input/keysym.js";
26
26
  import XtScancode from "./input/xtscancodes.js";
27
27
  import { encodings } from "./encodings.js";
28
- import "./util/polyfill.js";
29
28
 
30
29
  import RawDecoder from "./decoders/raw.js";
31
30
  import CopyRectDecoder from "./decoders/copyrect.js";
@@ -33,6 +32,7 @@ import RREDecoder from "./decoders/rre.js";
33
32
  import HextileDecoder from "./decoders/hextile.js";
34
33
  import TightDecoder from "./decoders/tight.js";
35
34
  import TightPNGDecoder from "./decoders/tightpng.js";
35
+ import ZRLEDecoder from "./decoders/zrle.js";
36
36
 
37
37
  // How many seconds to wait for a disconnect to finish
38
38
  const DISCONNECT_TIMEOUT = 3;
@@ -67,20 +67,25 @@ const extendedClipboardActionPeek = 1 << 26;
67
67
  const extendedClipboardActionNotify = 1 << 27;
68
68
  const extendedClipboardActionProvide = 1 << 28;
69
69
 
70
-
71
70
  export default class RFB extends EventTargetMixin {
72
- constructor(target, url, options) {
71
+ constructor(target, urlOrChannel, options) {
73
72
  if (!target) {
74
73
  throw new Error("Must specify target");
75
74
  }
76
- if (!url) {
77
- throw new Error("Must specify URL");
75
+ if (!urlOrChannel) {
76
+ throw new Error("Must specify URL, WebSocket or RTCDataChannel");
78
77
  }
79
78
 
80
79
  super();
81
80
 
82
81
  this._target = target;
83
- this._url = url;
82
+
83
+ if (typeof urlOrChannel === "string") {
84
+ this._url = urlOrChannel;
85
+ } else {
86
+ this._url = null;
87
+ this._rawChannel = urlOrChannel;
88
+ }
84
89
 
85
90
  // Connection details
86
91
  options = options || {};
@@ -130,6 +135,7 @@ export default class RFB extends EventTargetMixin {
130
135
  this._flushing = false; // Display flushing state
131
136
  this._keyboard = null; // Keyboard input handler object
132
137
  this._gestures = null; // Gesture input handler object
138
+ this._resizeObserver = null; // Resize observer object
133
139
 
134
140
  // Timers
135
141
  this._disconnTimer = null; // disconnection timer
@@ -167,7 +173,7 @@ export default class RFB extends EventTargetMixin {
167
173
  // Bound event handlers
168
174
  this._eventHandlers = {
169
175
  focusCanvas: this._focusCanvas.bind(this),
170
- windowResize: this._windowResize.bind(this),
176
+ handleResize: this._handleResize.bind(this),
171
177
  handleMouse: this._handleMouse.bind(this),
172
178
  handleWheel: this._handleWheel.bind(this),
173
179
  handleGesture: this._handleGesture.bind(this),
@@ -187,8 +193,6 @@ export default class RFB extends EventTargetMixin {
187
193
  this._canvas.style.margin = 'auto';
188
194
  // Some browsers add an outline on focus
189
195
  this._canvas.style.outline = 'none';
190
- // IE miscalculates width without this :(
191
- this._canvas.style.flexShrink = '0';
192
196
  this._canvas.width = 0;
193
197
  this._canvas.height = 0;
194
198
  this._canvas.tabIndex = -1;
@@ -215,6 +219,7 @@ export default class RFB extends EventTargetMixin {
215
219
  this._decoders[encodings.encodingHextile] = new HextileDecoder();
216
220
  this._decoders[encodings.encodingTight] = new TightDecoder();
217
221
  this._decoders[encodings.encodingTightPNG] = new TightPNGDecoder();
222
+ this._decoders[encodings.encodingZRLE] = new ZRLEDecoder();
218
223
 
219
224
  // NB: nothing that needs explicit teardown should be done
220
225
  // before this point, since this can throw an exception
@@ -232,58 +237,15 @@ export default class RFB extends EventTargetMixin {
232
237
  this._gestures = new GestureHandler();
233
238
 
234
239
  this._sock = new Websock();
235
- this._sock.on('message', () => {
236
- this._handleMessage();
237
- });
238
- this._sock.on('open', () => {
239
- if ((this._rfbConnectionState === 'connecting') &&
240
- (this._rfbInitState === '')) {
241
- this._rfbInitState = 'ProtocolVersion';
242
- Log.Debug("Starting VNC handshake");
243
- } else {
244
- this._fail("Unexpected server connection while " +
245
- this._rfbConnectionState);
246
- }
247
- });
248
- this._sock.on('close', (e) => {
249
- Log.Debug("WebSocket on-close event");
250
- let msg = "";
251
- if (e.code) {
252
- msg = "(code: " + e.code;
253
- if (e.reason) {
254
- msg += ", reason: " + e.reason;
255
- }
256
- msg += ")";
257
- }
258
- switch (this._rfbConnectionState) {
259
- case 'connecting':
260
- this._fail("Connection closed " + msg);
261
- break;
262
- case 'connected':
263
- // Handle disconnects that were initiated server-side
264
- this._updateConnectionState('disconnecting');
265
- this._updateConnectionState('disconnected');
266
- break;
267
- case 'disconnecting':
268
- // Normal disconnection path
269
- this._updateConnectionState('disconnected');
270
- break;
271
- case 'disconnected':
272
- this._fail("Unexpected server disconnect " +
273
- "when already disconnected " + msg);
274
- break;
275
- default:
276
- this._fail("Unexpected server disconnect before connecting " +
277
- msg);
278
- break;
279
- }
280
- this._sock.off('close');
281
- });
282
- this._sock.on('error', e => Log.Warn("WebSocket on-error event"));
240
+ this._sock.on('open', this._socketOpen.bind(this));
241
+ this._sock.on('close', this._socketClose.bind(this));
242
+ this._sock.on('message', this._handleMessage.bind(this));
243
+ this._sock.on('error', this._socketError.bind(this));
244
+
245
+ this._resizeObserver = new ResizeObserver(this._eventHandlers.handleResize);
283
246
 
284
- // Slight delay of the actual connection so that the caller has
285
- // time to set up callbacks
286
- setTimeout(this._updateConnectionState.bind(this, 'connecting'));
247
+ // All prepared, kick off the connection
248
+ this._updateConnectionState('connecting');
287
249
 
288
250
  Log.Debug("<< RFB.constructor");
289
251
 
@@ -472,8 +434,8 @@ export default class RFB extends EventTargetMixin {
472
434
  }
473
435
  }
474
436
 
475
- focus() {
476
- this._canvas.focus();
437
+ focus(options) {
438
+ this._canvas.focus(options);
477
439
  }
478
440
 
479
441
  blur() {
@@ -504,16 +466,22 @@ export default class RFB extends EventTargetMixin {
504
466
  _connect() {
505
467
  Log.Debug(">> RFB.connect");
506
468
 
507
- Log.Info("connecting to " + this._url);
508
-
509
- try {
510
- // WebSocket.onopen transitions to the RFB init states
469
+ if (this._url) {
470
+ Log.Info(`connecting to ${this._url}`);
511
471
  this._sock.open(this._url, this._wsProtocols);
512
- } catch (e) {
513
- if (e.name === 'SyntaxError') {
514
- this._fail("Invalid host or port (" + e + ")");
515
- } else {
516
- this._fail("Error when opening socket (" + e + ")");
472
+ } else {
473
+ Log.Info(`attaching ${this._rawChannel} to Websock`);
474
+ this._sock.attach(this._rawChannel);
475
+
476
+ if (this._sock.readyState === 'closed') {
477
+ throw Error("Cannot use already closed WebSocket/RTCDataChannel");
478
+ }
479
+
480
+ if (this._sock.readyState === 'open') {
481
+ // FIXME: _socketOpen() can in theory call _fail(), which
482
+ // isn't allowed this early, but I'm not sure that can
483
+ // happen without a bug messing up our state variables
484
+ this._socketOpen();
517
485
  }
518
486
  }
519
487
 
@@ -525,9 +493,8 @@ export default class RFB extends EventTargetMixin {
525
493
  this._cursor.attach(this._canvas);
526
494
  this._refreshCursor();
527
495
 
528
- // Monitor size changes of the screen
529
- // FIXME: Use ResizeObserver, or hidden overflow
530
- window.addEventListener('resize', this._eventHandlers.windowResize);
496
+ // Monitor size changes of the screen element
497
+ this._resizeObserver.observe(this._screen);
531
498
 
532
499
  // Always grab focus on some kind of click event
533
500
  this._canvas.addEventListener("mousedown", this._eventHandlers.focusCanvas);
@@ -568,7 +535,7 @@ export default class RFB extends EventTargetMixin {
568
535
  this._canvas.removeEventListener('contextmenu', this._eventHandlers.handleMouse);
569
536
  this._canvas.removeEventListener("mousedown", this._eventHandlers.focusCanvas);
570
537
  this._canvas.removeEventListener("touchstart", this._eventHandlers.focusCanvas);
571
- window.removeEventListener('resize', this._eventHandlers.windowResize);
538
+ this._resizeObserver.disconnect();
572
539
  this._keyboard.ungrab();
573
540
  this._gestures.detach();
574
541
  this._sock.close();
@@ -587,12 +554,64 @@ export default class RFB extends EventTargetMixin {
587
554
  Log.Debug("<< RFB.disconnect");
588
555
  }
589
556
 
557
+ _socketOpen() {
558
+ if ((this._rfbConnectionState === 'connecting') &&
559
+ (this._rfbInitState === '')) {
560
+ this._rfbInitState = 'ProtocolVersion';
561
+ Log.Debug("Starting VNC handshake");
562
+ } else {
563
+ this._fail("Unexpected server connection while " +
564
+ this._rfbConnectionState);
565
+ }
566
+ }
567
+
568
+ _socketClose(e) {
569
+ Log.Debug("WebSocket on-close event");
570
+ let msg = "";
571
+ if (e.code) {
572
+ msg = "(code: " + e.code;
573
+ if (e.reason) {
574
+ msg += ", reason: " + e.reason;
575
+ }
576
+ msg += ")";
577
+ }
578
+ switch (this._rfbConnectionState) {
579
+ case 'connecting':
580
+ this._fail("Connection closed " + msg);
581
+ break;
582
+ case 'connected':
583
+ // Handle disconnects that were initiated server-side
584
+ this._updateConnectionState('disconnecting');
585
+ this._updateConnectionState('disconnected');
586
+ break;
587
+ case 'disconnecting':
588
+ // Normal disconnection path
589
+ this._updateConnectionState('disconnected');
590
+ break;
591
+ case 'disconnected':
592
+ this._fail("Unexpected server disconnect " +
593
+ "when already disconnected " + msg);
594
+ break;
595
+ default:
596
+ this._fail("Unexpected server disconnect before connecting " +
597
+ msg);
598
+ break;
599
+ }
600
+ this._sock.off('close');
601
+ // Delete reference to raw channel to allow cleanup.
602
+ this._rawChannel = null;
603
+ }
604
+
605
+ _socketError(e) {
606
+ Log.Warn("WebSocket on-error event");
607
+ }
608
+
590
609
  _focusCanvas(event) {
591
610
  if (!this.focusOnClick) {
592
611
  return;
593
612
  }
594
613
 
595
- this.focus();
614
+ this.focus({ preventScroll: true });
596
615
  }
597
616
 
598
617
  _setDesktopName(name) {
@@ -602,7 +621,7 @@ export default class RFB extends EventTargetMixin {
602
621
  { detail: { name: this._fbName } }));
603
622
  }
604
623
 
605
- _windowResize(event) {
624
+ _handleResize() {
606
625
  // If the window resized then our screen element might have
607
626
  // as well. Update the viewport dimensions.
608
627
  window.requestAnimationFrame(() => {
@@ -1263,17 +1282,6 @@ export default class RFB extends EventTargetMixin {
1263
1282
  }
1264
1283
 
1265
1284
  _negotiateSecurity() {
1266
- // Polyfill since IE and PhantomJS doesn't have
1267
- // TypedArray.includes()
1268
- function includes(item, array) {
1269
- for (let i = 0; i < array.length; i++) {
1270
- if (array[i] === item) {
1271
- return true;
1272
- }
1273
- }
1274
- return false;
1275
- }
1276
-
1277
1285
  if (this._rfbVersion >= 3.7) {
1278
1286
  // Server sends supported list, client decides
1279
1287
  const numTypes = this._sock.rQshift8();
@@ -1290,15 +1298,15 @@ export default class RFB extends EventTargetMixin {
1290
1298
  Log.Debug("Server security types: " + types);
1291
1299
 
1292
1300
  // Look for each auth in preferred order
1293
- if (includes(1, types)) {
1301
+ if (types.includes(1)) {
1294
1302
  this._rfbAuthScheme = 1; // None
1295
- } else if (includes(22, types)) {
1303
+ } else if (types.includes(22)) {
1296
1304
  this._rfbAuthScheme = 22; // XVP
1297
- } else if (includes(16, types)) {
1305
+ } else if (types.includes(16)) {
1298
1306
  this._rfbAuthScheme = 16; // Tight
1299
- } else if (includes(2, types)) {
1307
+ } else if (types.includes(2)) {
1300
1308
  this._rfbAuthScheme = 2; // VNC Auth
1301
- } else if (includes(19, types)) {
1309
+ } else if (types.includes(19)) {
1302
1310
  this._rfbAuthScheme = 19; // VeNCrypt Auth
1303
1311
  } else {
1304
1312
  return this._fail("Unsupported security types (types: " + types + ")");
@@ -1441,8 +1449,8 @@ export default class RFB extends EventTargetMixin {
1441
1449
 
1442
1450
  // negotiated Plain subtype, server waits for password
1443
1451
  if (this._rfbVeNCryptState == 4) {
1444
- if (!this._rfbCredentials.username ||
1445
- !this._rfbCredentials.password) {
1452
+ if (this._rfbCredentials.username === undefined ||
1453
+ this._rfbCredentials.password === undefined) {
1446
1454
  this.dispatchEvent(new CustomEvent(
1447
1455
  "credentialsrequired",
1448
1456
  { detail: { types: ["username", "password"] } }));
@@ -1452,9 +1460,18 @@ export default class RFB extends EventTargetMixin {
1452
1460
  const user = encodeUTF8(this._rfbCredentials.username);
1453
1461
  const pass = encodeUTF8(this._rfbCredentials.password);
1454
1462
 
1455
- // XXX we assume lengths are <= 255 (should not be an issue in the real world)
1456
- this._sock.send([0, 0, 0, user.length]);
1457
- this._sock.send([0, 0, 0, pass.length]);
1463
+ this._sock.send([
1464
+ (user.length >> 24) & 0xFF,
1465
+ (user.length >> 16) & 0xFF,
1466
+ (user.length >> 8) & 0xFF,
1467
+ user.length & 0xFF
1468
+ ]);
1469
+ this._sock.send([
1470
+ (pass.length >> 24) & 0xFF,
1471
+ (pass.length >> 16) & 0xFF,
1472
+ (pass.length >> 8) & 0xFF,
1473
+ pass.length & 0xFF
1474
+ ]);
1458
1475
  this._sock.sendString(user);
1459
1476
  this._sock.sendString(pass);
1460
1477
 
@@ -1757,6 +1774,7 @@ export default class RFB extends EventTargetMixin {
1757
1774
  if (this._fbDepth == 24) {
1758
1775
  encs.push(encodings.encodingTight);
1759
1776
  encs.push(encodings.encodingTightPNG);
1777
+ encs.push(encodings.encodingZRLE);
1760
1778
  encs.push(encodings.encodingHextile);
1761
1779
  encs.push(encodings.encodingRRE);
1762
1780
  }
@@ -2183,15 +2201,7 @@ export default class RFB extends EventTargetMixin {
2183
2201
  return this._handleCursor();
2184
2202
 
2185
2203
  case encodings.pseudoEncodingQEMUExtendedKeyEvent:
2186
- // Old Safari doesn't support creating keyboard events
2187
- try {
2188
- const keyboardEvent = document.createEvent("keyboardEvent");
2189
- if (keyboardEvent.code !== undefined) {
2190
- this._qemuExtKeyEventSupported = true;
2191
- }
2192
- } catch (err) {
2193
- // Do nothing
2194
- }
2204
+ this._qemuExtKeyEventSupported = true;
2195
2205
  return true;
2196
2206
 
2197
2207
  case encodings.pseudoEncodingDesktopName:
@@ -2882,9 +2892,9 @@ RFB.messages = {
2882
2892
  buff[offset + 12] = 0; // blue-max
2883
2893
  buff[offset + 13] = (1 << bits) - 1; // blue-max
2884
2894
 
2885
- buff[offset + 14] = bits * 2; // red-shift
2895
+ buff[offset + 14] = bits * 0; // red-shift
2886
2896
  buff[offset + 15] = bits * 1; // green-shift
2887
- buff[offset + 16] = bits * 0; // blue-shift
2897
+ buff[offset + 16] = bits * 2; // blue-shift
2888
2898
 
2889
2899
  buff[offset + 17] = 0; // padding
2890
2900
  buff[offset + 18] = 0; // padding
@@ -45,15 +45,6 @@ try {
45
45
 
46
46
  export const supportsCursorURIs = _supportsCursorURIs;
47
47
 
48
- let _supportsImageMetadata = false;
49
- try {
50
- new ImageData(new Uint8ClampedArray(4), 1, 1);
51
- _supportsImageMetadata = true;
52
- } catch (ex) {
53
- // ignore failure
54
- }
55
- export const supportsImageMetadata = _supportsImageMetadata;
56
-
57
48
  let _hasScrollbarGutter = true;
58
49
  try {
59
50
  // Create invisible container
@@ -106,14 +97,6 @@ export function isSafari() {
106
97
  navigator.userAgent.indexOf('Chrome') === -1);
107
98
  }
108
99
 
109
- export function isIE() {
110
- return navigator && !!(/trident/i).exec(navigator.userAgent);
111
- }
112
-
113
- export function isEdge() {
114
- return navigator && !!(/edge/i).exec(navigator.userAgent);
115
- }
116
-
117
100
  export function isFirefox() {
118
101
  return navigator && !!(/firefox/i).exec(navigator.userAgent);
119
102
  }
@@ -43,9 +43,6 @@ export default class Cursor {
43
43
  if (useFallback) {
44
44
  document.body.appendChild(this._canvas);
45
45
 
46
- // FIXME: These don't fire properly except for mouse
47
- /// movement in IE. We want to also capture element
48
- // movement, size changes, visibility, etc.
49
46
  const options = { capture: true, passive: true };
50
47
  this._target.addEventListener('mouseover', this._eventHandlers.mouseover, options);
51
48
  this._target.addEventListener('mouseleave', this._eventHandlers.mouseleave, options);
@@ -90,14 +87,7 @@ export default class Cursor {
90
87
  this._canvas.width = w;
91
88
  this._canvas.height = h;
92
89
 
93
- let img;
94
- try {
95
- // IE doesn't support this
96
- img = new ImageData(new Uint8ClampedArray(rgba), w, h);
97
- } catch (ex) {
98
- img = ctx.createImageData(w, h);
99
- img.data.set(new Uint8ClampedArray(rgba));
100
- }
90
+ let img = new ImageData(new Uint8ClampedArray(rgba), w, h);
101
91
  ctx.clearRect(0, 0, w, h);
102
92
  ctx.putImageData(img, 0, 0);
103
93
 
@@ -65,10 +65,6 @@ export function setCapture(target) {
65
65
 
66
66
  target.setCapture();
67
67
  document.captureElement = target;
68
-
69
- // IE releases capture on 'click' events which might not trigger
70
- target.addEventListener('mouseup', releaseCapture);
71
-
72
68
  } else {
73
69
  // Release any existing capture in case this method is
74
70
  // called multiple times without coordination
package/core/websock.js CHANGED
@@ -1,10 +1,10 @@
1
1
  /*
2
- * Websock: high-performance binary WebSockets
2
+ * Websock: high-performance buffering wrapper
3
3
  * Copyright (C) 2019 The noVNC Authors
4
4
  * Licensed under MPL 2.0 (see LICENSE.txt)
5
5
  *
6
- * Websock is similar to the standard WebSocket object but with extra
7
- * buffer handling.
6
+ * Websock is similar to the standard WebSocket / RTCDataChannel object
7
+ * but with extra buffer handling.
8
8
  *
9
9
  * Websock has built-in receive queue buffering; the message event
10
10
  * does not contain actual data but is simply a notification that
@@ -17,14 +17,39 @@ import * as Log from './util/logging.js';
17
17
  // this has performance issues in some versions Chromium, and
18
18
  // doesn't gain a tremendous amount of performance increase in Firefox
19
19
  // at the moment. It may be valuable to turn it on in the future.
20
- // Also copyWithin() for TypedArrays is not supported in IE 11 or
21
- // Safari 13 (at the moment we want to support Safari 11).
22
- const ENABLE_COPYWITHIN = false;
23
20
  const MAX_RQ_GROW_SIZE = 40 * 1024 * 1024; // 40 MiB
24
21
 
22
+ // Constants pulled from RTCDataChannelState enum
23
+ // https://developer.mozilla.org/en-US/docs/Web/API/RTCDataChannel/readyState#RTCDataChannelState_enum
24
+ const DataChannel = {
25
+ CONNECTING: "connecting",
26
+ OPEN: "open",
27
+ CLOSING: "closing",
28
+ CLOSED: "closed"
29
+ };
30
+
31
+ const ReadyStates = {
32
+ CONNECTING: [WebSocket.CONNECTING, DataChannel.CONNECTING],
33
+ OPEN: [WebSocket.OPEN, DataChannel.OPEN],
34
+ CLOSING: [WebSocket.CLOSING, DataChannel.CLOSING],
35
+ CLOSED: [WebSocket.CLOSED, DataChannel.CLOSED],
36
+ };
37
+
38
+ // Properties a raw channel must have, WebSocket and RTCDataChannel are two examples
39
+ const rawChannelProps = [
40
+ "send",
41
+ "close",
42
+ "binaryType",
43
+ "onerror",
44
+ "onmessage",
45
+ "onopen",
46
+ "protocol",
47
+ "readyState",
48
+ ];
49
+
25
50
  export default class Websock {
26
51
  constructor() {
27
- this._websocket = null; // WebSocket object
52
+ this._websocket = null; // WebSocket or RTCDataChannel object
28
53
 
29
54
  this._rQi = 0; // Receive queue index
30
55
  this._rQlen = 0; // Next write position in the receive queue
@@ -46,6 +71,29 @@ export default class Websock {
46
71
  }
47
72
 
48
73
  // Getters and Setters
74
+
75
+ get readyState() {
76
+ let subState;
77
+
78
+ if (this._websocket === null) {
79
+ return "unused";
80
+ }
81
+
82
+ subState = this._websocket.readyState;
83
+
84
+ if (ReadyStates.CONNECTING.includes(subState)) {
85
+ return "connecting";
86
+ } else if (ReadyStates.OPEN.includes(subState)) {
87
+ return "open";
88
+ } else if (ReadyStates.CLOSING.includes(subState)) {
89
+ return "closing";
90
+ } else if (ReadyStates.CLOSED.includes(subState)) {
91
+ return "closed";
92
+ }
93
+
94
+ return "unknown";
95
+ }
96
+
49
97
  get sQ() {
50
98
  return this._sQ;
51
99
  }
@@ -143,7 +191,7 @@ export default class Websock {
143
191
  // Send Queue
144
192
 
145
193
  flush() {
146
- if (this._sQlen > 0 && this._websocket.readyState === WebSocket.OPEN) {
194
+ if (this._sQlen > 0 && this.readyState === 'open') {
147
195
  this._websocket.send(this._encodeMessage());
148
196
  this._sQlen = 0;
149
197
  }
@@ -180,12 +228,25 @@ export default class Websock {
180
228
  }
181
229
 
182
230
  open(uri, protocols) {
231
+ this.attach(new WebSocket(uri, protocols));
232
+ }
233
+
234
+ attach(rawChannel) {
183
235
  this.init();
184
236
 
185
- this._websocket = new WebSocket(uri, protocols);
186
- this._websocket.binaryType = 'arraybuffer';
237
+ // Must get object and class methods to be compatible with the tests.
238
+ const channelProps = [...Object.keys(rawChannel), ...Object.getOwnPropertyNames(Object.getPrototypeOf(rawChannel))];
239
+ for (let i = 0; i < rawChannelProps.length; i++) {
240
+ const prop = rawChannelProps[i];
241
+ if (channelProps.indexOf(prop) < 0) {
242
+ throw new Error('Raw channel missing property: ' + prop);
243
+ }
244
+ }
187
245
 
246
+ this._websocket = rawChannel;
247
+ this._websocket.binaryType = "arraybuffer";
188
248
  this._websocket.onmessage = this._recvMessage.bind(this);
249
+
189
250
  this._websocket.onopen = () => {
190
251
  Log.Debug('>> WebSock.onopen');
191
252
  if (this._websocket.protocol) {
@@ -195,11 +256,13 @@ export default class Websock {
195
256
  this._eventHandlers.open();
196
257
  Log.Debug("<< WebSock.onopen");
197
258
  };
259
+
198
260
  this._websocket.onclose = (e) => {
199
261
  Log.Debug(">> WebSock.onclose");
200
262
  this._eventHandlers.close(e);
201
263
  Log.Debug("<< WebSock.onclose");
202
264
  };
265
+
203
266
  this._websocket.onerror = (e) => {
204
267
  Log.Debug(">> WebSock.onerror: " + e);
205
268
  this._eventHandlers.error(e);
@@ -209,8 +272,8 @@ export default class Websock {
209
272
 
210
273
  close() {
211
274
  if (this._websocket) {
212
- if ((this._websocket.readyState === WebSocket.OPEN) ||
213
- (this._websocket.readyState === WebSocket.CONNECTING)) {
275
+ if (this.readyState === 'connecting' ||
276
+ this.readyState === 'open') {
214
277
  Log.Info("Closing WebSocket connection");
215
278
  this._websocket.close();
216
279
  }
@@ -256,11 +319,7 @@ export default class Websock {
256
319
  this._rQ = new Uint8Array(this._rQbufferSize);
257
320
  this._rQ.set(new Uint8Array(oldRQbuffer, this._rQi, this._rQlen - this._rQi));
258
321
  } else {
259
- if (ENABLE_COPYWITHIN) {
260
- this._rQ.copyWithin(0, this._rQi, this._rQlen);
261
- } else {
262
- this._rQ.set(new Uint8Array(this._rQ.buffer, this._rQi, this._rQlen - this._rQi));
263
- }
322
+ this._rQ.copyWithin(0, this._rQi, this._rQlen);
264
323
  }
265
324
 
266
325
  this._rQlen = this._rQlen - this._rQi;
package/docs/API.md CHANGED
@@ -165,9 +165,9 @@ connection to a specified VNC server.
165
165
  existing contents of the `HTMLElement` will be untouched, but new
166
166
  elements will be added during the lifetime of the `RFB` object.
167
167
 
168
- **`url`**
168
+ **`urlOrDataChannel`**
169
169
  - A `DOMString` specifying the VNC server to connect to. This must be
170
- a valid WebSocket URL.
170
+ a valid WebSocket URL. This can also be a `WebSocket` or `RTCDataChannel`.
171
171
 
172
172
  **`options`** *Optional*
173
173
  - An `Object` specifying extra details about how the connection
@@ -328,7 +328,14 @@ Keyboard events will be sent to the remote server after this point.
328
328
 
329
329
  ##### Syntax
330
330
 
331
- RFB.focus( );
331
+ RFB.focus( [options] );
332
+
333
+ ###### Parameters
334
+
335
+ **`options`** *Optional*
336
+ - A `object` providing options to control how the focus will be
337
+ performed. Please see [`HTMLElement.focus()`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus)
338
+ for available options.
332
339
 
333
340
  #### RFB.blur()
334
341
 
package/docs/LIBRARY.md CHANGED
@@ -18,18 +18,14 @@ do things.
18
18
 
19
19
  ## Conversion of Modules
20
20
 
21
- noVNC is written using ECMAScript 6 modules. Many of the major browsers support
22
- these modules natively, but not all. They are also not supported by Node.js. To
23
- use noVNC in these places the library must first be converted.
21
+ noVNC is written using ECMAScript 6 modules. This is not supported by older
22
+ versions of Node.js. To use noVNC with those older versions of Node.js the
23
+ library must first be converted.
24
24
 
25
25
  Fortunately noVNC includes a script to handle this conversion. Please follow
26
26
  the following steps:
27
27
 
28
28
  1. Install Node.js
29
29
  2. Run `npm install` in the noVNC directory
30
- 3. Run `./utils/use_require.js --as <module format>`
31
-
32
- Several module formats are available. Please run
33
- `./utils/use_require.js --help` to see them all.
34
30
 
35
31
  The result of the conversion is available in the `lib/` directory.