@novnc/novnc 1.3.0-g7ad4e60 → 1.3.0-g80a7c1d

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.
package/README.md CHANGED
@@ -113,6 +113,11 @@ proxy.
113
113
  used to specify the location of a running VNC server:
114
114
 
115
115
  `./utils/novnc_proxy --vnc localhost:5901`
116
+
117
+ * If you don't need to expose the web server to public internet, you can
118
+ bind to localhost:
119
+
120
+ `./utils/novnc_proxy --vnc localhost:5901 --listen localhost:6081`
116
121
 
117
122
  * Point your browser to the cut-and-paste URL that is output by the `novnc_proxy`
118
123
  script. Hit the Connect button, enter a password if the VNC server has one
@@ -123,13 +128,17 @@ Running the command below will install the latest release of noVNC from Snap:
123
128
 
124
129
  `sudo snap install novnc`
125
130
 
126
- #### Running noVNC
131
+ #### Running noVNC from Snap Directly
127
132
 
128
133
  You can run the Snap-package installed novnc directly with, for example:
129
134
 
130
135
  `novnc --listen 6081 --vnc localhost:5901 # /snap/bin/novnc if /snap/bin is not in your PATH`
131
136
 
132
- #### Running as a Service (Daemon)
137
+ If you want to use certificate files, due to standard Snap confinement restrictions you need to have them in the /home/\<user\>/snap/novnc/current/ directory. If your username is jsmith an example command would be:
138
+
139
+ `novnc --listen 8443 --cert ~jsmith/snap/novnc/current/self.crt --key ~jsmith/snap/novnc/current/self.key --vnc ubuntu.example.com:5901`
140
+
141
+ #### Running noVNC from Snap as a Service (Daemon)
133
142
  The Snap package also has the capability to run a 'novnc' service which can be
134
143
  configured to listen on multiple ports connecting to multiple VNC servers
135
144
  (effectively a service runing multiple instances of novnc).
@@ -0,0 +1,141 @@
1
+ /*
2
+ * noVNC: HTML5 VNC client
3
+ * Copyright (C) 2019 The noVNC Authors
4
+ * Licensed under MPL 2.0 (see LICENSE.txt)
5
+ *
6
+ * See README.md for usage and integration instructions.
7
+ *
8
+ */
9
+
10
+ export default class JPEGDecoder {
11
+ constructor() {
12
+ // RealVNC will reuse the quantization tables
13
+ // and Huffman tables, so we need to cache them.
14
+ this._quantTables = [];
15
+ this._huffmanTables = [];
16
+ this._cachedQuantTables = [];
17
+ this._cachedHuffmanTables = [];
18
+
19
+ this._jpegLength = 0;
20
+ this._segments = [];
21
+ }
22
+
23
+ decodeRect(x, y, width, height, sock, display, depth) {
24
+ // A rect of JPEG encodings is simply a JPEG file
25
+ if (!this._parseJPEG(sock.rQslice(0))) {
26
+ return false;
27
+ }
28
+ const data = sock.rQshiftBytes(this._jpegLength);
29
+ if (this._quantTables.length != 0 && this._huffmanTables.length != 0) {
30
+ // If there are quantization tables and Huffman tables in the JPEG
31
+ // image, we can directly render it.
32
+ display.imageRect(x, y, width, height, "image/jpeg", data);
33
+ return true;
34
+ } else {
35
+ // Otherwise we need to insert cached tables.
36
+ const sofIndex = this._segments.findIndex(
37
+ x => x[1] == 0xC0 || x[1] == 0xC2
38
+ );
39
+ if (sofIndex == -1) {
40
+ throw new Error("Illegal JPEG image without SOF");
41
+ }
42
+ let segments = this._segments.slice(0, sofIndex);
43
+ segments = segments.concat(this._quantTables.length ?
44
+ this._quantTables :
45
+ this._cachedQuantTables);
46
+ segments.push(this._segments[sofIndex]);
47
+ segments = segments.concat(this._huffmanTables.length ?
48
+ this._huffmanTables :
49
+ this._cachedHuffmanTables,
50
+ this._segments.slice(sofIndex + 1));
51
+ let length = 0;
52
+ for (let i = 0; i < segments.length; i++) {
53
+ length += segments[i].length;
54
+ }
55
+ const data = new Uint8Array(length);
56
+ length = 0;
57
+ for (let i = 0; i < segments.length; i++) {
58
+ data.set(segments[i], length);
59
+ length += segments[i].length;
60
+ }
61
+ display.imageRect(x, y, width, height, "image/jpeg", data);
62
+ return true;
63
+ }
64
+ }
65
+
66
+ _parseJPEG(buffer) {
67
+ if (this._quantTables.length != 0) {
68
+ this._cachedQuantTables = this._quantTables;
69
+ }
70
+ if (this._huffmanTables.length != 0) {
71
+ this._cachedHuffmanTables = this._huffmanTables;
72
+ }
73
+ this._quantTables = [];
74
+ this._huffmanTables = [];
75
+ this._segments = [];
76
+ let i = 0;
77
+ let bufferLength = buffer.length;
78
+ while (true) {
79
+ let j = i;
80
+ if (j + 2 > bufferLength) {
81
+ return false;
82
+ }
83
+ if (buffer[j] != 0xFF) {
84
+ throw new Error("Illegal JPEG marker received (byte: " +
85
+ buffer[j] + ")");
86
+ }
87
+ const type = buffer[j+1];
88
+ j += 2;
89
+ if (type == 0xD9) {
90
+ this._jpegLength = j;
91
+ this._segments.push(buffer.slice(i, j));
92
+ return true;
93
+ } else if (type == 0xDA) {
94
+ // start of scan
95
+ let hasFoundEndOfScan = false;
96
+ for (let k = j + 3; k + 1 < bufferLength; k++) {
97
+ if (buffer[k] == 0xFF && buffer[k+1] != 0x00 &&
98
+ !(buffer[k+1] >= 0xD0 && buffer[k+1] <= 0xD7)) {
99
+ j = k;
100
+ hasFoundEndOfScan = true;
101
+ break;
102
+ }
103
+ }
104
+ if (!hasFoundEndOfScan) {
105
+ return false;
106
+ }
107
+ this._segments.push(buffer.slice(i, j));
108
+ i = j;
109
+ continue;
110
+ } else if (type >= 0xD0 && type < 0xD9 || type == 0x01) {
111
+ // No length after marker
112
+ this._segments.push(buffer.slice(i, j));
113
+ i = j;
114
+ continue;
115
+ }
116
+ if (j + 2 > bufferLength) {
117
+ return false;
118
+ }
119
+ const length = (buffer[j] << 8) + buffer[j+1] - 2;
120
+ if (length < 0) {
121
+ throw new Error("Illegal JPEG length received (length: " +
122
+ length + ")");
123
+ }
124
+ j += 2;
125
+ if (j + length > bufferLength) {
126
+ return false;
127
+ }
128
+ j += length;
129
+ const segment = buffer.slice(i, j);
130
+ if (type == 0xC4) {
131
+ // Huffman tables
132
+ this._huffmanTables.push(segment);
133
+ } else if (type == 0xDB) {
134
+ // Quantization tables
135
+ this._quantTables.push(segment);
136
+ }
137
+ this._segments.push(segment);
138
+ i = j;
139
+ }
140
+ }
141
+ }
@@ -0,0 +1,185 @@
1
+ /*
2
+ * noVNC: HTML5 VNC client
3
+ * Copyright (C) 2021 The noVNC Authors
4
+ * Licensed under MPL 2.0 (see LICENSE.txt)
5
+ *
6
+ * See README.md for usage and integration instructions.
7
+ *
8
+ */
9
+
10
+ import Inflate from "../inflator.js";
11
+
12
+ const ZRLE_TILE_WIDTH = 64;
13
+ const ZRLE_TILE_HEIGHT = 64;
14
+
15
+ export default class ZRLEDecoder {
16
+ constructor() {
17
+ this._length = 0;
18
+ this._inflator = new Inflate();
19
+
20
+ this._pixelBuffer = new Uint8Array(ZRLE_TILE_WIDTH * ZRLE_TILE_HEIGHT * 4);
21
+ this._tileBuffer = new Uint8Array(ZRLE_TILE_WIDTH * ZRLE_TILE_HEIGHT * 4);
22
+ }
23
+
24
+ decodeRect(x, y, width, height, sock, display, depth) {
25
+ if (this._length === 0) {
26
+ if (sock.rQwait("ZLib data length", 4)) {
27
+ return false;
28
+ }
29
+ this._length = sock.rQshift32();
30
+ }
31
+ if (sock.rQwait("Zlib data", this._length)) {
32
+ return false;
33
+ }
34
+
35
+ const data = sock.rQshiftBytes(this._length);
36
+
37
+ this._inflator.setInput(data);
38
+
39
+ for (let ty = y; ty < y + height; ty += ZRLE_TILE_HEIGHT) {
40
+ let th = Math.min(ZRLE_TILE_HEIGHT, y + height - ty);
41
+
42
+ for (let tx = x; tx < x + width; tx += ZRLE_TILE_WIDTH) {
43
+ let tw = Math.min(ZRLE_TILE_WIDTH, x + width - tx);
44
+
45
+ const tileSize = tw * th;
46
+ const subencoding = this._inflator.inflate(1)[0];
47
+ if (subencoding === 0) {
48
+ // raw data
49
+ const data = this._readPixels(tileSize);
50
+ display.blitImage(tx, ty, tw, th, data, 0, false);
51
+ } else if (subencoding === 1) {
52
+ // solid
53
+ const background = this._readPixels(1);
54
+ display.fillRect(tx, ty, tw, th, [background[0], background[1], background[2]]);
55
+ } else if (subencoding >= 2 && subencoding <= 16) {
56
+ const data = this._decodePaletteTile(subencoding, tileSize, tw, th);
57
+ display.blitImage(tx, ty, tw, th, data, 0, false);
58
+ } else if (subencoding === 128) {
59
+ const data = this._decodeRLETile(tileSize);
60
+ display.blitImage(tx, ty, tw, th, data, 0, false);
61
+ } else if (subencoding >= 130 && subencoding <= 255) {
62
+ const data = this._decodeRLEPaletteTile(subencoding - 128, tileSize);
63
+ display.blitImage(tx, ty, tw, th, data, 0, false);
64
+ } else {
65
+ throw new Error('Unknown subencoding: ' + subencoding);
66
+ }
67
+ }
68
+ }
69
+ this._length = 0;
70
+ return true;
71
+ }
72
+
73
+ _getBitsPerPixelInPalette(paletteSize) {
74
+ if (paletteSize <= 2) {
75
+ return 1;
76
+ } else if (paletteSize <= 4) {
77
+ return 2;
78
+ } else if (paletteSize <= 16) {
79
+ return 4;
80
+ }
81
+ }
82
+
83
+ _readPixels(pixels) {
84
+ let data = this._pixelBuffer;
85
+ const buffer = this._inflator.inflate(3*pixels);
86
+ for (let i = 0, j = 0; i < pixels*4; i += 4, j += 3) {
87
+ data[i] = buffer[j];
88
+ data[i + 1] = buffer[j + 1];
89
+ data[i + 2] = buffer[j + 2];
90
+ data[i + 3] = 255; // Add the Alpha
91
+ }
92
+ return data;
93
+ }
94
+
95
+ _decodePaletteTile(paletteSize, tileSize, tilew, tileh) {
96
+ const data = this._tileBuffer;
97
+ const palette = this._readPixels(paletteSize);
98
+ const bitsPerPixel = this._getBitsPerPixelInPalette(paletteSize);
99
+ const mask = (1 << bitsPerPixel) - 1;
100
+
101
+ let offset = 0;
102
+ let encoded = this._inflator.inflate(1)[0];
103
+
104
+ for (let y=0; y<tileh; y++) {
105
+ let shift = 8-bitsPerPixel;
106
+ for (let x=0; x<tilew; x++) {
107
+ if (shift<0) {
108
+ shift=8-bitsPerPixel;
109
+ encoded = this._inflator.inflate(1)[0];
110
+ }
111
+ let indexInPalette = (encoded>>shift) & mask;
112
+
113
+ data[offset] = palette[indexInPalette * 4];
114
+ data[offset + 1] = palette[indexInPalette * 4 + 1];
115
+ data[offset + 2] = palette[indexInPalette * 4 + 2];
116
+ data[offset + 3] = palette[indexInPalette * 4 + 3];
117
+ offset += 4;
118
+ shift-=bitsPerPixel;
119
+ }
120
+ if (shift<8-bitsPerPixel && y<tileh-1) {
121
+ encoded = this._inflator.inflate(1)[0];
122
+ }
123
+ }
124
+ return data;
125
+ }
126
+
127
+ _decodeRLETile(tileSize) {
128
+ const data = this._tileBuffer;
129
+ let i = 0;
130
+ while (i < tileSize) {
131
+ const pixel = this._readPixels(1);
132
+ const length = this._readRLELength();
133
+ for (let j = 0; j < length; j++) {
134
+ data[i * 4] = pixel[0];
135
+ data[i * 4 + 1] = pixel[1];
136
+ data[i * 4 + 2] = pixel[2];
137
+ data[i * 4 + 3] = pixel[3];
138
+ i++;
139
+ }
140
+ }
141
+ return data;
142
+ }
143
+
144
+ _decodeRLEPaletteTile(paletteSize, tileSize) {
145
+ const data = this._tileBuffer;
146
+
147
+ // palette
148
+ const palette = this._readPixels(paletteSize);
149
+
150
+ let offset = 0;
151
+ while (offset < tileSize) {
152
+ let indexInPalette = this._inflator.inflate(1)[0];
153
+ let length = 1;
154
+ if (indexInPalette >= 128) {
155
+ indexInPalette -= 128;
156
+ length = this._readRLELength();
157
+ }
158
+ if (indexInPalette > paletteSize) {
159
+ throw new Error('Too big index in palette: ' + indexInPalette + ', palette size: ' + paletteSize);
160
+ }
161
+ if (offset + length > tileSize) {
162
+ throw new Error('Too big rle length in palette mode: ' + length + ', allowed length is: ' + (tileSize - offset));
163
+ }
164
+
165
+ for (let j = 0; j < length; j++) {
166
+ data[offset * 4] = palette[indexInPalette * 4];
167
+ data[offset * 4 + 1] = palette[indexInPalette * 4 + 1];
168
+ data[offset * 4 + 2] = palette[indexInPalette * 4 + 2];
169
+ data[offset * 4 + 3] = palette[indexInPalette * 4 + 3];
170
+ offset++;
171
+ }
172
+ }
173
+ return data;
174
+ }
175
+
176
+ _readRLELength() {
177
+ let length = 0;
178
+ let current = 0;
179
+ do {
180
+ current = this._inflator.inflate(1)[0];
181
+ length += current;
182
+ } while (current === 255);
183
+ return length + 1;
184
+ }
185
+ }
package/core/encodings.js CHANGED
@@ -12,7 +12,9 @@ export const encodings = {
12
12
  encodingRRE: 2,
13
13
  encodingHextile: 5,
14
14
  encodingTight: 7,
15
+ encodingZRLE: 16,
15
16
  encodingTightPNG: -260,
17
+ encodingJPEG: 21,
16
18
 
17
19
  pseudoEncodingQualityLevel9: -23,
18
20
  pseudoEncodingQualityLevel0: -32,
@@ -38,7 +40,9 @@ export function encodingName(num) {
38
40
  case encodings.encodingRRE: return "RRE";
39
41
  case encodings.encodingHextile: return "Hextile";
40
42
  case encodings.encodingTight: return "Tight";
43
+ case encodings.encodingZRLE: return "ZRLE";
41
44
  case encodings.encodingTightPNG: return "TightPNG";
45
+ case encodings.encodingJPEG: return "JPEG";
42
46
  default: return "[unknown encoding " + num + "]";
43
47
  }
44
48
  }
package/core/rfb.js CHANGED
@@ -32,6 +32,8 @@ import RREDecoder from "./decoders/rre.js";
32
32
  import HextileDecoder from "./decoders/hextile.js";
33
33
  import TightDecoder from "./decoders/tight.js";
34
34
  import TightPNGDecoder from "./decoders/tightpng.js";
35
+ import ZRLEDecoder from "./decoders/zrle.js";
36
+ import JPEGDecoder from "./decoders/jpeg.js";
35
37
 
36
38
  // How many seconds to wait for a disconnect to finish
37
39
  const DISCONNECT_TIMEOUT = 3;
@@ -218,6 +220,8 @@ export default class RFB extends EventTargetMixin {
218
220
  this._decoders[encodings.encodingHextile] = new HextileDecoder();
219
221
  this._decoders[encodings.encodingTight] = new TightDecoder();
220
222
  this._decoders[encodings.encodingTightPNG] = new TightPNGDecoder();
223
+ this._decoders[encodings.encodingZRLE] = new ZRLEDecoder();
224
+ this._decoders[encodings.encodingJPEG] = new JPEGDecoder();
221
225
 
222
226
  // NB: nothing that needs explicit teardown should be done
223
227
  // before this point, since this can throw an exception
@@ -240,6 +244,8 @@ export default class RFB extends EventTargetMixin {
240
244
  this._sock.on('message', this._handleMessage.bind(this));
241
245
  this._sock.on('error', this._socketError.bind(this));
242
246
 
247
+ this._expectedClientWidth = null;
248
+ this._expectedClientHeight = null;
243
249
  this._resizeObserver = new ResizeObserver(this._eventHandlers.handleResize);
244
250
 
245
251
  // All prepared, kick off the connection
@@ -619,7 +625,26 @@ export default class RFB extends EventTargetMixin {
619
625
  { detail: { name: this._fbName } }));
620
626
  }
621
627
 
628
+ _saveExpectedClientSize() {
629
+ this._expectedClientWidth = this._screen.clientWidth;
630
+ this._expectedClientHeight = this._screen.clientHeight;
631
+ }
632
+
633
+ _currentClientSize() {
634
+ return [this._screen.clientWidth, this._screen.clientHeight];
635
+ }
636
+
637
+ _clientHasExpectedSize() {
638
+ const [currentWidth, currentHeight] = this._currentClientSize();
639
+ return currentWidth == this._expectedClientWidth &&
640
+ currentHeight == this._expectedClientHeight;
641
+ }
642
+
622
643
  _handleResize() {
644
+ // Don't change anything if the client size is already as expected
645
+ if (this._clientHasExpectedSize()) {
646
+ return;
647
+ }
623
648
  // If the window resized then our screen element might have
624
649
  // as well. Update the viewport dimensions.
625
650
  window.requestAnimationFrame(() => {
@@ -660,6 +685,12 @@ export default class RFB extends EventTargetMixin {
660
685
  this._display.viewportChangeSize(size.w, size.h);
661
686
  this._fixScrollbars();
662
687
  }
688
+
689
+ // When changing clipping we might show or hide scrollbars.
690
+ // This causes the expected client dimensions to change.
691
+ if (curClip !== newClip) {
692
+ this._saveExpectedClientSize();
693
+ }
663
694
  }
664
695
 
665
696
  _updateScale() {
@@ -684,6 +715,7 @@ export default class RFB extends EventTargetMixin {
684
715
  }
685
716
 
686
717
  const size = this._screenSize();
718
+
687
719
  RFB.messages.setDesktopSize(this._sock,
688
720
  Math.floor(size.w), Math.floor(size.h),
689
721
  this._screenID, this._screenFlags);
@@ -699,12 +731,13 @@ export default class RFB extends EventTargetMixin {
699
731
  }
700
732
 
701
733
  _fixScrollbars() {
702
- // This is a hack because Chrome screws up the calculation
703
- // for when scrollbars are needed. So to fix it we temporarily
704
- // toggle them off and on.
734
+ // This is a hack because Safari on macOS screws up the calculation
735
+ // for when scrollbars are needed. We get scrollbars when making the
736
+ // browser smaller, despite remote resize being enabled. So to fix it
737
+ // we temporarily toggle them off and on.
705
738
  const orig = this._screen.style.overflow;
706
739
  this._screen.style.overflow = 'hidden';
707
- // Force Chrome to recalculate the layout by asking for
740
+ // Force Safari to recalculate the layout by asking for
708
741
  // an element's dimensions
709
742
  this._screen.getBoundingClientRect();
710
743
  this._screen.style.overflow = orig;
@@ -1772,6 +1805,8 @@ export default class RFB extends EventTargetMixin {
1772
1805
  if (this._fbDepth == 24) {
1773
1806
  encs.push(encodings.encodingTight);
1774
1807
  encs.push(encodings.encodingTightPNG);
1808
+ encs.push(encodings.encodingZRLE);
1809
+ encs.push(encodings.encodingJPEG);
1775
1810
  encs.push(encodings.encodingHextile);
1776
1811
  encs.push(encodings.encodingRRE);
1777
1812
  }
@@ -2500,6 +2535,9 @@ export default class RFB extends EventTargetMixin {
2500
2535
  this._updateScale();
2501
2536
 
2502
2537
  this._updateContinuousUpdates();
2538
+
2539
+ // Keep this size until browser client size changes
2540
+ this._saveExpectedClientSize();
2503
2541
  }
2504
2542
 
2505
2543
  _xvpOp(ver, op) {
package/lib/base64.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
 
3
- function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
3
+ function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); }
4
4
 
5
5
  Object.defineProperty(exports, "__esModule", {
6
6
  value: true
@@ -9,7 +9,7 @@ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Cons
9
9
 
10
10
  function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
11
11
 
12
- function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
12
+ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; }
13
13
 
14
14
  /*
15
15
  * noVNC: HTML5 VNC client
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
 
3
- function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
3
+ function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); }
4
4
 
5
5
  Object.defineProperty(exports, "__esModule", {
6
6
  value: true
@@ -17,7 +17,7 @@ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Cons
17
17
 
18
18
  function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
19
19
 
20
- function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
20
+ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; }
21
21
 
22
22
  var HextileDecoder = /*#__PURE__*/function () {
23
23
  function HextileDecoder() {