Merge branch 'novnc:master' into patch-1
This commit is contained in:
commit
4311930796
|
|
@ -1,21 +1,22 @@
|
||||||
{
|
{
|
||||||
|
"HTTPS is required for full functionality": "",
|
||||||
"Connecting...": "En cours de connexion...",
|
"Connecting...": "En cours de connexion...",
|
||||||
"Disconnecting...": "Déconnexion en cours...",
|
"Disconnecting...": "Déconnexion en cours...",
|
||||||
"Reconnecting...": "Reconnexion en cours...",
|
"Reconnecting...": "Reconnexion en cours...",
|
||||||
"Internal error": "Erreur interne",
|
"Internal error": "Erreur interne",
|
||||||
"Must set host": "Doit définir l'hôte",
|
"Must set host": "Doit définir l'hôte",
|
||||||
"Connected (encrypted) to ": "Connecté (crypté) à ",
|
"Connected (encrypted) to ": "Connecté (chiffré) à ",
|
||||||
"Connected (unencrypted) to ": "Connecté (non crypté) à ",
|
"Connected (unencrypted) to ": "Connecté (non chiffré) à ",
|
||||||
"Something went wrong, connection is closed": "Quelque chose est arrivé, la connexion est fermée",
|
"Something went wrong, connection is closed": "Quelque chose s'est mal passé, la connexion a été fermée",
|
||||||
"Failed to connect to server": "Échec de connexion au serveur",
|
"Failed to connect to server": "Échec de connexion au serveur",
|
||||||
"Disconnected": "Déconnecté",
|
"Disconnected": "Déconnecté",
|
||||||
"New connection has been rejected with reason: ": "Une nouvelle connexion a été rejetée avec raison: ",
|
"New connection has been rejected with reason: ": "Une nouvelle connexion a été rejetée avec motif : ",
|
||||||
"New connection has been rejected": "Une nouvelle connexion a été rejetée",
|
"New connection has been rejected": "Une nouvelle connexion a été rejetée",
|
||||||
"Credentials are required": "Les identifiants sont requis",
|
"Credentials are required": "Les identifiants sont requis",
|
||||||
"noVNC encountered an error:": "noVNC a rencontré une erreur:",
|
"noVNC encountered an error:": "noVNC a rencontré une erreur :",
|
||||||
"Hide/Show the control bar": "Masquer/Afficher la barre de contrôle",
|
"Hide/Show the control bar": "Masquer/Afficher la barre de contrôle",
|
||||||
"Drag": "Faire glisser",
|
"Drag": "Faire glisser",
|
||||||
"Move/Drag Viewport": "Déplacer/faire glisser Viewport",
|
"Move/Drag Viewport": "Déplacer/faire glisser le Viewport",
|
||||||
"Keyboard": "Clavier",
|
"Keyboard": "Clavier",
|
||||||
"Show Keyboard": "Afficher le clavier",
|
"Show Keyboard": "Afficher le clavier",
|
||||||
"Extra keys": "Touches supplémentaires",
|
"Extra keys": "Touches supplémentaires",
|
||||||
|
|
@ -39,34 +40,39 @@
|
||||||
"Reboot": "Redémarrer",
|
"Reboot": "Redémarrer",
|
||||||
"Reset": "Réinitialiser",
|
"Reset": "Réinitialiser",
|
||||||
"Clipboard": "Presse-papiers",
|
"Clipboard": "Presse-papiers",
|
||||||
"Clear": "Effacer",
|
"Edit clipboard content in the textarea below.": "",
|
||||||
"Fullscreen": "Plein écran",
|
|
||||||
"Settings": "Paramètres",
|
"Settings": "Paramètres",
|
||||||
"Shared Mode": "Mode partagé",
|
"Shared Mode": "Mode partagé",
|
||||||
"View Only": "Afficher uniquement",
|
"View Only": "Afficher uniquement",
|
||||||
"Clip to Window": "Clip à fenêtre",
|
"Clip to Window": "Clip à fenêtre",
|
||||||
"Scaling Mode:": "Mode mise à l'échelle:",
|
"Scaling Mode:": "Mode mise à l'échelle :",
|
||||||
"None": "Aucun",
|
"None": "Aucun",
|
||||||
"Local Scaling": "Mise à l'échelle locale",
|
"Local Scaling": "Mise à l'échelle locale",
|
||||||
"Remote Resizing": "Redimensionnement à distance",
|
"Remote Resizing": "Redimensionnement à distance",
|
||||||
"Advanced": "Avancé",
|
"Advanced": "Avancé",
|
||||||
"Quality:": "Qualité:",
|
"Quality:": "Qualité :",
|
||||||
"Compression level:": "Niveau de compression:",
|
"Compression level:": "Niveau de compression :",
|
||||||
"Repeater ID:": "ID Répéteur:",
|
"Repeater ID:": "ID Répéteur :",
|
||||||
"WebSocket": "WebSocket",
|
"WebSocket": "WebSocket",
|
||||||
"Encrypt": "Crypter",
|
"Encrypt": "Chiffrer",
|
||||||
"Host:": "Hôte:",
|
"Host:": "Hôte :",
|
||||||
"Port:": "Port:",
|
"Port:": "Port :",
|
||||||
"Path:": "Chemin:",
|
"Path:": "Chemin :",
|
||||||
"Automatic Reconnect": "Reconnecter automatiquemen",
|
"Automatic Reconnect": "Reconnecter automatiquemen",
|
||||||
"Reconnect Delay (ms):": "Délai de reconnexion (ms):",
|
"Reconnect Delay (ms):": "Délai de reconnexion (ms) :",
|
||||||
"Show Dot when No Cursor": "Afficher le point lorsqu'il n'y a pas de curseur",
|
"Show Dot when No Cursor": "Afficher le point lorsqu'il n'y a pas de curseur",
|
||||||
"Logging:": "Se connecter:",
|
"Logging:": "Se connecter :",
|
||||||
"Version:": "Version:",
|
"Version:": "Version :",
|
||||||
"Disconnect": "Déconnecter",
|
"Disconnect": "Déconnecter",
|
||||||
"Connect": "Connecter",
|
"Connect": "Connecter",
|
||||||
"Username:": "Nom d'utilisateur:",
|
"Server identity": "",
|
||||||
"Password:": "Mot de passe:",
|
"The server has provided the following identifying information:": "",
|
||||||
|
"Fingerprint:": "",
|
||||||
|
"Please verify that the information is correct and press \"Approve\". Otherwise press \"Reject\".": "",
|
||||||
|
"Approve": "",
|
||||||
|
"Reject": "",
|
||||||
|
"Username:": "Nom d'utilisateur :",
|
||||||
|
"Password:": "Mot de passe :",
|
||||||
"Send Credentials": "Envoyer les identifiants",
|
"Send Credentials": "Envoyer les identifiants",
|
||||||
"Cancel": "Annuler"
|
"Cancel": "Annuler"
|
||||||
}
|
}
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
{
|
{
|
||||||
|
"HTTPS is required for full functionality": "HTTPS krävs för full funktionalitet",
|
||||||
"Connecting...": "Ansluter...",
|
"Connecting...": "Ansluter...",
|
||||||
"Disconnecting...": "Kopplar ner...",
|
"Disconnecting...": "Kopplar ner...",
|
||||||
"Reconnecting...": "Återansluter...",
|
"Reconnecting...": "Återansluter...",
|
||||||
|
|
@ -39,8 +40,8 @@
|
||||||
"Reboot": "Boota om",
|
"Reboot": "Boota om",
|
||||||
"Reset": "Återställ",
|
"Reset": "Återställ",
|
||||||
"Clipboard": "Urklipp",
|
"Clipboard": "Urklipp",
|
||||||
"Clear": "Rensa",
|
"Edit clipboard content in the textarea below.": "Redigera urklippets innehåll i fältet nedan.",
|
||||||
"Fullscreen": "Fullskärm",
|
"Full Screen": "Fullskärm",
|
||||||
"Settings": "Inställningar",
|
"Settings": "Inställningar",
|
||||||
"Shared Mode": "Delat Läge",
|
"Shared Mode": "Delat Läge",
|
||||||
"View Only": "Endast Visning",
|
"View Only": "Endast Visning",
|
||||||
|
|
@ -65,6 +66,13 @@
|
||||||
"Version:": "Version:",
|
"Version:": "Version:",
|
||||||
"Disconnect": "Koppla från",
|
"Disconnect": "Koppla från",
|
||||||
"Connect": "Anslut",
|
"Connect": "Anslut",
|
||||||
|
"Server identity": "Server-identitet",
|
||||||
|
"The server has provided the following identifying information:": "Servern har gett följande identifierande information:",
|
||||||
|
"Fingerprint:": "Fingeravtryck:",
|
||||||
|
"Please verify that the information is correct and press \"Approve\". Otherwise press \"Reject\".": "Kontrollera att informationen är korrekt och tryck sedan \"Godkänn\". Tryck annars \"Neka\".",
|
||||||
|
"Approve": "Godkänn",
|
||||||
|
"Reject": "Neka",
|
||||||
|
"Credentials": "Användaruppgifter",
|
||||||
"Username:": "Användarnamn:",
|
"Username:": "Användarnamn:",
|
||||||
"Password:": "Lösenord:",
|
"Password:": "Lösenord:",
|
||||||
"Send Credentials": "Skicka Användaruppgifter",
|
"Send Credentials": "Skicka Användaruppgifter",
|
||||||
|
|
|
||||||
|
|
@ -16,13 +16,19 @@ export class Localizer {
|
||||||
this.language = 'en';
|
this.language = 'en';
|
||||||
|
|
||||||
// Current dictionary of translations
|
// Current dictionary of translations
|
||||||
this.dictionary = undefined;
|
this._dictionary = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Configure suitable language based on user preferences
|
// Configure suitable language based on user preferences
|
||||||
setup(supportedLanguages) {
|
async setup(supportedLanguages, baseURL) {
|
||||||
this.language = 'en'; // Default: US English
|
this.language = 'en'; // Default: US English
|
||||||
|
this._dictionary = undefined;
|
||||||
|
|
||||||
|
this._setupLanguage(supportedLanguages);
|
||||||
|
await this._setupDictionary(baseURL);
|
||||||
|
}
|
||||||
|
|
||||||
|
_setupLanguage(supportedLanguages) {
|
||||||
/*
|
/*
|
||||||
* Navigator.languages only available in Chrome (32+) and FireFox (32+)
|
* Navigator.languages only available in Chrome (32+) and FireFox (32+)
|
||||||
* Fall back to navigator.language for other browsers
|
* Fall back to navigator.language for other browsers
|
||||||
|
|
@ -40,12 +46,6 @@ export class Localizer {
|
||||||
.replace("_", "-")
|
.replace("_", "-")
|
||||||
.split("-");
|
.split("-");
|
||||||
|
|
||||||
// Built-in default?
|
|
||||||
if ((userLang[0] === 'en') &&
|
|
||||||
((userLang[1] === undefined) || (userLang[1] === 'us'))) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// First pass: perfect match
|
// First pass: perfect match
|
||||||
for (let j = 0; j < supportedLanguages.length; j++) {
|
for (let j = 0; j < supportedLanguages.length; j++) {
|
||||||
const supLang = supportedLanguages[j]
|
const supLang = supportedLanguages[j]
|
||||||
|
|
@ -64,7 +64,12 @@ export class Localizer {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Second pass: fallback
|
// Second pass: English fallback
|
||||||
|
if (userLang[0] === 'en') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Third pass pass: other fallback
|
||||||
for (let j = 0;j < supportedLanguages.length;j++) {
|
for (let j = 0;j < supportedLanguages.length;j++) {
|
||||||
const supLang = supportedLanguages[j]
|
const supLang = supportedLanguages[j]
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
|
|
@ -84,10 +89,32 @@ export class Localizer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async _setupDictionary(baseURL) {
|
||||||
|
if (baseURL) {
|
||||||
|
if (!baseURL.endsWith("/")) {
|
||||||
|
baseURL = baseURL + "/";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
baseURL = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.language === "en") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let response = await fetch(baseURL + this.language + ".json");
|
||||||
|
if (!response.ok) {
|
||||||
|
throw Error("" + response.status + " " + response.statusText);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._dictionary = await response.json();
|
||||||
|
}
|
||||||
|
|
||||||
// Retrieve localised text
|
// Retrieve localised text
|
||||||
get(id) {
|
get(id) {
|
||||||
if (typeof this.dictionary !== 'undefined' && this.dictionary[id]) {
|
if (typeof this._dictionary !== 'undefined' &&
|
||||||
return this.dictionary[id];
|
this._dictionary[id]) {
|
||||||
|
return this._dictionary[id];
|
||||||
} else {
|
} else {
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -661,7 +661,7 @@ html {
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-content: center;
|
align-content: center;
|
||||||
|
|
||||||
line-height: 25px;
|
line-height: 1.6;
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
|
|
||||||
|
|
@ -887,7 +887,7 @@ html {
|
||||||
.noVNC_logo {
|
.noVNC_logo {
|
||||||
color:yellow;
|
color:yellow;
|
||||||
font-family: 'Orbitron', 'OrbitronTTF', sans-serif;
|
font-family: 'Orbitron', 'OrbitronTTF', sans-serif;
|
||||||
line-height:90%;
|
line-height: 0.9;
|
||||||
text-shadow: 0.1em 0.1em 0 black;
|
text-shadow: 0.1em 0.1em 0 black;
|
||||||
}
|
}
|
||||||
.noVNC_logo span{
|
.noVNC_logo span{
|
||||||
|
|
|
||||||
|
|
@ -86,13 +86,16 @@ option {
|
||||||
* Checkboxes
|
* Checkboxes
|
||||||
*/
|
*/
|
||||||
input[type=checkbox] {
|
input[type=checkbox] {
|
||||||
position: relative;
|
display: inline-flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
background-color: white;
|
background-color: white;
|
||||||
background-image: unset;
|
background-image: unset;
|
||||||
border: 1px solid dimgrey;
|
border: 1px solid dimgrey;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
width: 13px;
|
width: 13px;
|
||||||
height: 13px;
|
height: 13px;
|
||||||
|
padding: 0;
|
||||||
margin-right: 6px;
|
margin-right: 6px;
|
||||||
vertical-align: bottom;
|
vertical-align: bottom;
|
||||||
transition: 0.2s background-color linear;
|
transition: 0.2s background-color linear;
|
||||||
|
|
@ -103,14 +106,12 @@ input[type=checkbox]:checked {
|
||||||
}
|
}
|
||||||
input[type=checkbox]:checked::after {
|
input[type=checkbox]:checked::after {
|
||||||
content: "";
|
content: "";
|
||||||
position: absolute;
|
display: block; /* width & height doesn't work on inline elements */
|
||||||
top: 0;
|
|
||||||
left: 3px;
|
|
||||||
width: 3px;
|
width: 3px;
|
||||||
height: 7px;
|
height: 7px;
|
||||||
border: 1px solid white;
|
border: 1px solid white;
|
||||||
border-width: 0 2px 2px 0;
|
border-width: 0 2px 2px 0;
|
||||||
transform: rotate(40deg);
|
transform: rotate(40deg) translateY(-1px);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
||||||
18
app/ui.js
18
app/ui.js
|
|
@ -1763,20 +1763,8 @@ const UI = {
|
||||||
|
|
||||||
// Set up translations
|
// Set up translations
|
||||||
const LINGUAS = ["cs", "de", "el", "es", "fr", "it", "ja", "ko", "nl", "pl", "pt_BR", "ru", "sv", "tr", "zh_CN", "zh_TW"];
|
const LINGUAS = ["cs", "de", "el", "es", "fr", "it", "ja", "ko", "nl", "pl", "pt_BR", "ru", "sv", "tr", "zh_CN", "zh_TW"];
|
||||||
l10n.setup(LINGUAS);
|
l10n.setup(LINGUAS, "app/locale/")
|
||||||
if (l10n.language === "en" || l10n.dictionary !== undefined) {
|
.catch(err => Log.Error("Failed to load translations: " + err))
|
||||||
UI.prime();
|
.then(UI.prime);
|
||||||
} else {
|
|
||||||
fetch('app/locale/' + l10n.language + '.json')
|
|
||||||
.then((response) => {
|
|
||||||
if (!response.ok) {
|
|
||||||
throw Error("" + response.status + " " + response.statusText);
|
|
||||||
}
|
|
||||||
return response.json();
|
|
||||||
})
|
|
||||||
.then((translations) => { l10n.dictionary = translations; })
|
|
||||||
.catch(err => Log.Error("Failed to load translations: " + err))
|
|
||||||
.then(UI.prime);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default UI;
|
export default UI;
|
||||||
|
|
|
||||||
|
|
@ -6,16 +6,16 @@
|
||||||
* See README.md for usage and integration instructions.
|
* See README.md for usage and integration instructions.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { initLogging as mainInitLogging } from '../core/util/logging.js';
|
import * as Log from '../core/util/logging.js';
|
||||||
|
|
||||||
// init log level reading the logging HTTP param
|
// init log level reading the logging HTTP param
|
||||||
export function initLogging(level) {
|
export function initLogging(level) {
|
||||||
"use strict";
|
"use strict";
|
||||||
if (typeof level !== "undefined") {
|
if (typeof level !== "undefined") {
|
||||||
mainInitLogging(level);
|
Log.initLogging(level);
|
||||||
} else {
|
} else {
|
||||||
const param = document.location.href.match(/logging=([A-Za-z0-9._-]*)/);
|
const param = document.location.href.match(/logging=([A-Za-z0-9._-]*)/);
|
||||||
mainInitLogging(param || undefined);
|
Log.initLogging(param || undefined);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -25,14 +25,14 @@ export function initLogging(level) {
|
||||||
//
|
//
|
||||||
// For privacy (Using a hastag #, the parameters will not be sent to the server)
|
// For privacy (Using a hastag #, the parameters will not be sent to the server)
|
||||||
// the url can be requested in the following way:
|
// the url can be requested in the following way:
|
||||||
// https://www.example.com#myqueryparam=myvalue&password=secreatvalue
|
// https://www.example.com#myqueryparam=myvalue&password=secretvalue
|
||||||
//
|
//
|
||||||
// Even Mixing public and non public parameters will work:
|
// Even Mixing public and non public parameters will work:
|
||||||
// https://www.example.com?nonsecretparam=example.com#password=secreatvalue
|
// https://www.example.com?nonsecretparam=example.com#password=secretvalue
|
||||||
export function getQueryVar(name, defVal) {
|
export function getQueryVar(name, defVal) {
|
||||||
"use strict";
|
"use strict";
|
||||||
const re = new RegExp('.*[?&]' + name + '=([^&#]*)'),
|
const re = new RegExp('.*[?&]' + name + '=([^&#]*)'),
|
||||||
match = ''.concat(document.location.href, window.location.hash).match(re);
|
match = document.location.href.match(re);
|
||||||
if (typeof defVal === 'undefined') { defVal = null; }
|
if (typeof defVal === 'undefined') { defVal = null; }
|
||||||
|
|
||||||
if (match) {
|
if (match) {
|
||||||
|
|
@ -146,7 +146,7 @@ export function writeSetting(name, value) {
|
||||||
if (window.chrome && window.chrome.storage) {
|
if (window.chrome && window.chrome.storage) {
|
||||||
window.chrome.storage.sync.set(settings);
|
window.chrome.storage.sync.set(settings);
|
||||||
} else {
|
} else {
|
||||||
localStorage.setItem(name, value);
|
localStorageSet(name, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -156,7 +156,7 @@ export function readSetting(name, defaultValue) {
|
||||||
if ((name in settings) || (window.chrome && window.chrome.storage)) {
|
if ((name in settings) || (window.chrome && window.chrome.storage)) {
|
||||||
value = settings[name];
|
value = settings[name];
|
||||||
} else {
|
} else {
|
||||||
value = localStorage.getItem(name);
|
value = localStorageGet(name);
|
||||||
settings[name] = value;
|
settings[name] = value;
|
||||||
}
|
}
|
||||||
if (typeof value === "undefined") {
|
if (typeof value === "undefined") {
|
||||||
|
|
@ -181,6 +181,70 @@ export function eraseSetting(name) {
|
||||||
if (window.chrome && window.chrome.storage) {
|
if (window.chrome && window.chrome.storage) {
|
||||||
window.chrome.storage.sync.remove(name);
|
window.chrome.storage.sync.remove(name);
|
||||||
} else {
|
} else {
|
||||||
localStorage.removeItem(name);
|
localStorageRemove(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let loggedMsgs = [];
|
||||||
|
function logOnce(msg, level = "warn") {
|
||||||
|
if (!loggedMsgs.includes(msg)) {
|
||||||
|
switch (level) {
|
||||||
|
case "error":
|
||||||
|
Log.Error(msg);
|
||||||
|
break;
|
||||||
|
case "warn":
|
||||||
|
Log.Warn(msg);
|
||||||
|
break;
|
||||||
|
case "debug":
|
||||||
|
Log.Debug(msg);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Log.Info(msg);
|
||||||
|
}
|
||||||
|
loggedMsgs.push(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let cookiesMsg = "Couldn't access noVNC settings, are cookies disabled?";
|
||||||
|
|
||||||
|
function localStorageGet(name) {
|
||||||
|
let r;
|
||||||
|
try {
|
||||||
|
r = localStorage.getItem(name);
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof DOMException) {
|
||||||
|
logOnce(cookiesMsg);
|
||||||
|
logOnce("'localStorage.getItem(" + name + ")' failed: " + e,
|
||||||
|
"debug");
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
function localStorageSet(name, value) {
|
||||||
|
try {
|
||||||
|
localStorage.setItem(name, value);
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof DOMException) {
|
||||||
|
logOnce(cookiesMsg);
|
||||||
|
logOnce("'localStorage.setItem(" + name + "," + value +
|
||||||
|
")' failed: " + e, "debug");
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function localStorageRemove(name) {
|
||||||
|
try {
|
||||||
|
localStorage.removeItem(name);
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof DOMException) {
|
||||||
|
logOnce(cookiesMsg);
|
||||||
|
logOnce("'localStorage.removeItem(" + name + ")' failed: " + e,
|
||||||
|
"debug");
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,178 @@
|
||||||
|
export class AESECBCipher {
|
||||||
|
constructor() {
|
||||||
|
this._key = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
get algorithm() {
|
||||||
|
return { name: "AES-ECB" };
|
||||||
|
}
|
||||||
|
|
||||||
|
static async importKey(key, _algorithm, extractable, keyUsages) {
|
||||||
|
const cipher = new AESECBCipher;
|
||||||
|
await cipher._importKey(key, extractable, keyUsages);
|
||||||
|
return cipher;
|
||||||
|
}
|
||||||
|
|
||||||
|
async _importKey(key, extractable, keyUsages) {
|
||||||
|
this._key = await window.crypto.subtle.importKey(
|
||||||
|
"raw", key, {name: "AES-CBC"}, extractable, keyUsages);
|
||||||
|
}
|
||||||
|
|
||||||
|
async encrypt(_algorithm, plaintext) {
|
||||||
|
const x = new Uint8Array(plaintext);
|
||||||
|
if (x.length % 16 !== 0 || this._key === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const n = x.length / 16;
|
||||||
|
for (let i = 0; i < n; i++) {
|
||||||
|
const y = new Uint8Array(await window.crypto.subtle.encrypt({
|
||||||
|
name: "AES-CBC",
|
||||||
|
iv: new Uint8Array(16),
|
||||||
|
}, this._key, x.slice(i * 16, i * 16 + 16))).slice(0, 16);
|
||||||
|
x.set(y, i * 16);
|
||||||
|
}
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AESEAXCipher {
|
||||||
|
constructor() {
|
||||||
|
this._rawKey = null;
|
||||||
|
this._ctrKey = null;
|
||||||
|
this._cbcKey = null;
|
||||||
|
this._zeroBlock = new Uint8Array(16);
|
||||||
|
this._prefixBlock0 = this._zeroBlock;
|
||||||
|
this._prefixBlock1 = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]);
|
||||||
|
this._prefixBlock2 = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
get algorithm() {
|
||||||
|
return { name: "AES-EAX" };
|
||||||
|
}
|
||||||
|
|
||||||
|
async _encryptBlock(block) {
|
||||||
|
const encrypted = await window.crypto.subtle.encrypt({
|
||||||
|
name: "AES-CBC",
|
||||||
|
iv: this._zeroBlock,
|
||||||
|
}, this._cbcKey, block);
|
||||||
|
return new Uint8Array(encrypted).slice(0, 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
async _initCMAC() {
|
||||||
|
const k1 = await this._encryptBlock(this._zeroBlock);
|
||||||
|
const k2 = new Uint8Array(16);
|
||||||
|
const v = k1[0] >>> 6;
|
||||||
|
for (let i = 0; i < 15; i++) {
|
||||||
|
k2[i] = (k1[i + 1] >> 6) | (k1[i] << 2);
|
||||||
|
k1[i] = (k1[i + 1] >> 7) | (k1[i] << 1);
|
||||||
|
}
|
||||||
|
const lut = [0x0, 0x87, 0x0e, 0x89];
|
||||||
|
k2[14] ^= v >>> 1;
|
||||||
|
k2[15] = (k1[15] << 2) ^ lut[v];
|
||||||
|
k1[15] = (k1[15] << 1) ^ lut[v >> 1];
|
||||||
|
this._k1 = k1;
|
||||||
|
this._k2 = k2;
|
||||||
|
}
|
||||||
|
|
||||||
|
async _encryptCTR(data, counter) {
|
||||||
|
const encrypted = await window.crypto.subtle.encrypt({
|
||||||
|
name: "AES-CTR",
|
||||||
|
counter: counter,
|
||||||
|
length: 128
|
||||||
|
}, this._ctrKey, data);
|
||||||
|
return new Uint8Array(encrypted);
|
||||||
|
}
|
||||||
|
|
||||||
|
async _decryptCTR(data, counter) {
|
||||||
|
const decrypted = await window.crypto.subtle.decrypt({
|
||||||
|
name: "AES-CTR",
|
||||||
|
counter: counter,
|
||||||
|
length: 128
|
||||||
|
}, this._ctrKey, data);
|
||||||
|
return new Uint8Array(decrypted);
|
||||||
|
}
|
||||||
|
|
||||||
|
async _computeCMAC(data, prefixBlock) {
|
||||||
|
if (prefixBlock.length !== 16) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const n = Math.floor(data.length / 16);
|
||||||
|
const m = Math.ceil(data.length / 16);
|
||||||
|
const r = data.length - n * 16;
|
||||||
|
const cbcData = new Uint8Array((m + 1) * 16);
|
||||||
|
cbcData.set(prefixBlock);
|
||||||
|
cbcData.set(data, 16);
|
||||||
|
if (r === 0) {
|
||||||
|
for (let i = 0; i < 16; i++) {
|
||||||
|
cbcData[n * 16 + i] ^= this._k1[i];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cbcData[(n + 1) * 16 + r] = 0x80;
|
||||||
|
for (let i = 0; i < 16; i++) {
|
||||||
|
cbcData[(n + 1) * 16 + i] ^= this._k2[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let cbcEncrypted = await window.crypto.subtle.encrypt({
|
||||||
|
name: "AES-CBC",
|
||||||
|
iv: this._zeroBlock,
|
||||||
|
}, this._cbcKey, cbcData);
|
||||||
|
|
||||||
|
cbcEncrypted = new Uint8Array(cbcEncrypted);
|
||||||
|
const mac = cbcEncrypted.slice(cbcEncrypted.length - 32, cbcEncrypted.length - 16);
|
||||||
|
return mac;
|
||||||
|
}
|
||||||
|
|
||||||
|
static async importKey(key, _algorithm, _extractable, _keyUsages) {
|
||||||
|
const cipher = new AESEAXCipher;
|
||||||
|
await cipher._importKey(key);
|
||||||
|
return cipher;
|
||||||
|
}
|
||||||
|
|
||||||
|
async _importKey(key) {
|
||||||
|
this._rawKey = key;
|
||||||
|
this._ctrKey = await window.crypto.subtle.importKey(
|
||||||
|
"raw", key, {name: "AES-CTR"}, false, ["encrypt", "decrypt"]);
|
||||||
|
this._cbcKey = await window.crypto.subtle.importKey(
|
||||||
|
"raw", key, {name: "AES-CBC"}, false, ["encrypt"]);
|
||||||
|
await this._initCMAC();
|
||||||
|
}
|
||||||
|
|
||||||
|
async encrypt(algorithm, message) {
|
||||||
|
const ad = algorithm.additionalData;
|
||||||
|
const nonce = algorithm.iv;
|
||||||
|
const nCMAC = await this._computeCMAC(nonce, this._prefixBlock0);
|
||||||
|
const encrypted = await this._encryptCTR(message, nCMAC);
|
||||||
|
const adCMAC = await this._computeCMAC(ad, this._prefixBlock1);
|
||||||
|
const mac = await this._computeCMAC(encrypted, this._prefixBlock2);
|
||||||
|
for (let i = 0; i < 16; i++) {
|
||||||
|
mac[i] ^= nCMAC[i] ^ adCMAC[i];
|
||||||
|
}
|
||||||
|
const res = new Uint8Array(16 + encrypted.length);
|
||||||
|
res.set(encrypted);
|
||||||
|
res.set(mac, encrypted.length);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
async decrypt(algorithm, data) {
|
||||||
|
const encrypted = data.slice(0, data.length - 16);
|
||||||
|
const ad = algorithm.additionalData;
|
||||||
|
const nonce = algorithm.iv;
|
||||||
|
const mac = data.slice(data.length - 16);
|
||||||
|
const nCMAC = await this._computeCMAC(nonce, this._prefixBlock0);
|
||||||
|
const adCMAC = await this._computeCMAC(ad, this._prefixBlock1);
|
||||||
|
const computedMac = await this._computeCMAC(encrypted, this._prefixBlock2);
|
||||||
|
for (let i = 0; i < 16; i++) {
|
||||||
|
computedMac[i] ^= nCMAC[i] ^ adCMAC[i];
|
||||||
|
}
|
||||||
|
if (computedMac.length !== mac.length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
for (let i = 0; i < mac.length; i++) {
|
||||||
|
if (computedMac[i] !== mac[i]) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const res = await this._decryptCTR(encrypted, nCMAC);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
export function modPow(b, e, m) {
|
||||||
|
let r = 1n;
|
||||||
|
b = b % m;
|
||||||
|
while (e > 0n) {
|
||||||
|
if ((e & 1n) === 1n) {
|
||||||
|
r = (r * b) % m;
|
||||||
|
}
|
||||||
|
e = e >> 1n;
|
||||||
|
b = (b * b) % m;
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function bigIntToU8Array(bigint, padLength=0) {
|
||||||
|
let hex = bigint.toString(16);
|
||||||
|
if (padLength === 0) {
|
||||||
|
padLength = Math.ceil(hex.length / 2);
|
||||||
|
}
|
||||||
|
hex = hex.padStart(padLength * 2, '0');
|
||||||
|
const length = hex.length / 2;
|
||||||
|
const arr = new Uint8Array(length);
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
arr[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
||||||
|
}
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function u8ArrayToBigInt(arr) {
|
||||||
|
let hex = '0x';
|
||||||
|
for (let i = 0; i < arr.length; i++) {
|
||||||
|
hex += arr[i].toString(16).padStart(2, '0');
|
||||||
|
}
|
||||||
|
return BigInt(hex);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,90 @@
|
||||||
|
import { AESECBCipher, AESEAXCipher } from "./aes.js";
|
||||||
|
import { DESCBCCipher, DESECBCipher } from "./des.js";
|
||||||
|
import { RSACipher } from "./rsa.js";
|
||||||
|
import { DHCipher } from "./dh.js";
|
||||||
|
import { MD5 } from "./md5.js";
|
||||||
|
|
||||||
|
// A single interface for the cryptographic algorithms not supported by SubtleCrypto.
|
||||||
|
// Both synchronous and asynchronous implmentations are allowed.
|
||||||
|
class LegacyCrypto {
|
||||||
|
constructor() {
|
||||||
|
this._algorithms = {
|
||||||
|
"AES-ECB": AESECBCipher,
|
||||||
|
"AES-EAX": AESEAXCipher,
|
||||||
|
"DES-ECB": DESECBCipher,
|
||||||
|
"DES-CBC": DESCBCCipher,
|
||||||
|
"RSA-PKCS1-v1_5": RSACipher,
|
||||||
|
"DH": DHCipher,
|
||||||
|
"MD5": MD5,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
encrypt(algorithm, key, data) {
|
||||||
|
if (key.algorithm.name !== algorithm.name) {
|
||||||
|
throw new Error("algorithm does not match");
|
||||||
|
}
|
||||||
|
if (typeof key.encrypt !== "function") {
|
||||||
|
throw new Error("key does not support encryption");
|
||||||
|
}
|
||||||
|
return key.encrypt(algorithm, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
decrypt(algorithm, key, data) {
|
||||||
|
if (key.algorithm.name !== algorithm.name) {
|
||||||
|
throw new Error("algorithm does not match");
|
||||||
|
}
|
||||||
|
if (typeof key.decrypt !== "function") {
|
||||||
|
throw new Error("key does not support encryption");
|
||||||
|
}
|
||||||
|
return key.decrypt(algorithm, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
importKey(format, keyData, algorithm, extractable, keyUsages) {
|
||||||
|
if (format !== "raw") {
|
||||||
|
throw new Error("key format is not supported");
|
||||||
|
}
|
||||||
|
const alg = this._algorithms[algorithm.name];
|
||||||
|
if (typeof alg === "undefined" || typeof alg.importKey !== "function") {
|
||||||
|
throw new Error("algorithm is not supported");
|
||||||
|
}
|
||||||
|
return alg.importKey(keyData, algorithm, extractable, keyUsages);
|
||||||
|
}
|
||||||
|
|
||||||
|
generateKey(algorithm, extractable, keyUsages) {
|
||||||
|
const alg = this._algorithms[algorithm.name];
|
||||||
|
if (typeof alg === "undefined" || typeof alg.generateKey !== "function") {
|
||||||
|
throw new Error("algorithm is not supported");
|
||||||
|
}
|
||||||
|
return alg.generateKey(algorithm, extractable, keyUsages);
|
||||||
|
}
|
||||||
|
|
||||||
|
exportKey(format, key) {
|
||||||
|
if (format !== "raw") {
|
||||||
|
throw new Error("key format is not supported");
|
||||||
|
}
|
||||||
|
if (typeof key.exportKey !== "function") {
|
||||||
|
throw new Error("key does not support exportKey");
|
||||||
|
}
|
||||||
|
return key.exportKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
digest(algorithm, data) {
|
||||||
|
const alg = this._algorithms[algorithm];
|
||||||
|
if (typeof alg !== "function") {
|
||||||
|
throw new Error("algorithm is not supported");
|
||||||
|
}
|
||||||
|
return alg(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
deriveBits(algorithm, key, length) {
|
||||||
|
if (key.algorithm.name !== algorithm.name) {
|
||||||
|
throw new Error("algorithm does not match");
|
||||||
|
}
|
||||||
|
if (typeof key.deriveBits !== "function") {
|
||||||
|
throw new Error("key does not support deriveBits");
|
||||||
|
}
|
||||||
|
return key.deriveBits(algorithm, length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new LegacyCrypto;
|
||||||
|
|
@ -128,7 +128,7 @@ const SP8 = [b|f,z|e,a|z,c|f,b|z,b|f,z|d,b|z,a|d,c|z,c|f,a|e,c|e,a|f,z|e,z|d,
|
||||||
|
|
||||||
/* eslint-enable comma-spacing */
|
/* eslint-enable comma-spacing */
|
||||||
|
|
||||||
export default class DES {
|
class DES {
|
||||||
constructor(password) {
|
constructor(password) {
|
||||||
this.keys = [];
|
this.keys = [];
|
||||||
|
|
||||||
|
|
@ -258,9 +258,73 @@ export default class DES {
|
||||||
}
|
}
|
||||||
return b;
|
return b;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Encrypt 16 bytes of text using passwd as key
|
export class DESECBCipher {
|
||||||
encrypt(t) {
|
constructor() {
|
||||||
return this.enc8(t.slice(0, 8)).concat(this.enc8(t.slice(8, 16)));
|
this._cipher = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
get algorithm() {
|
||||||
|
return { name: "DES-ECB" };
|
||||||
|
}
|
||||||
|
|
||||||
|
static importKey(key, _algorithm, _extractable, _keyUsages) {
|
||||||
|
const cipher = new DESECBCipher;
|
||||||
|
cipher._importKey(key);
|
||||||
|
return cipher;
|
||||||
|
}
|
||||||
|
|
||||||
|
_importKey(key, _extractable, _keyUsages) {
|
||||||
|
this._cipher = new DES(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
encrypt(_algorithm, plaintext) {
|
||||||
|
const x = new Uint8Array(plaintext);
|
||||||
|
if (x.length % 8 !== 0 || this._cipher === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const n = x.length / 8;
|
||||||
|
for (let i = 0; i < n; i++) {
|
||||||
|
x.set(this._cipher.enc8(x.slice(i * 8, i * 8 + 8)), i * 8);
|
||||||
|
}
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DESCBCCipher {
|
||||||
|
constructor() {
|
||||||
|
this._cipher = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
get algorithm() {
|
||||||
|
return { name: "DES-CBC" };
|
||||||
|
}
|
||||||
|
|
||||||
|
static importKey(key, _algorithm, _extractable, _keyUsages) {
|
||||||
|
const cipher = new DESCBCCipher;
|
||||||
|
cipher._importKey(key);
|
||||||
|
return cipher;
|
||||||
|
}
|
||||||
|
|
||||||
|
_importKey(key) {
|
||||||
|
this._cipher = new DES(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
encrypt(algorithm, plaintext) {
|
||||||
|
const x = new Uint8Array(plaintext);
|
||||||
|
let y = new Uint8Array(algorithm.iv);
|
||||||
|
if (x.length % 8 !== 0 || this._cipher === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const n = x.length / 8;
|
||||||
|
for (let i = 0; i < n; i++) {
|
||||||
|
for (let j = 0; j < 8; j++) {
|
||||||
|
y[j] ^= plaintext[i * 8 + j];
|
||||||
|
}
|
||||||
|
y = this._cipher.enc8(y);
|
||||||
|
x.set(y, i * 8);
|
||||||
|
}
|
||||||
|
return x;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
import { modPow, bigIntToU8Array, u8ArrayToBigInt } from "./bigint.js";
|
||||||
|
|
||||||
|
class DHPublicKey {
|
||||||
|
constructor(key) {
|
||||||
|
this._key = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
get algorithm() {
|
||||||
|
return { name: "DH" };
|
||||||
|
}
|
||||||
|
|
||||||
|
exportKey() {
|
||||||
|
return this._key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DHCipher {
|
||||||
|
constructor() {
|
||||||
|
this._g = null;
|
||||||
|
this._p = null;
|
||||||
|
this._gBigInt = null;
|
||||||
|
this._pBigInt = null;
|
||||||
|
this._privateKey = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
get algorithm() {
|
||||||
|
return { name: "DH" };
|
||||||
|
}
|
||||||
|
|
||||||
|
static generateKey(algorithm, _extractable) {
|
||||||
|
const cipher = new DHCipher;
|
||||||
|
cipher._generateKey(algorithm);
|
||||||
|
return { privateKey: cipher, publicKey: new DHPublicKey(cipher._publicKey) };
|
||||||
|
}
|
||||||
|
|
||||||
|
_generateKey(algorithm) {
|
||||||
|
const g = algorithm.g;
|
||||||
|
const p = algorithm.p;
|
||||||
|
this._keyBytes = p.length;
|
||||||
|
this._gBigInt = u8ArrayToBigInt(g);
|
||||||
|
this._pBigInt = u8ArrayToBigInt(p);
|
||||||
|
this._privateKey = window.crypto.getRandomValues(new Uint8Array(this._keyBytes));
|
||||||
|
this._privateKeyBigInt = u8ArrayToBigInt(this._privateKey);
|
||||||
|
this._publicKey = bigIntToU8Array(modPow(
|
||||||
|
this._gBigInt, this._privateKeyBigInt, this._pBigInt), this._keyBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
deriveBits(algorithm, length) {
|
||||||
|
const bytes = Math.ceil(length / 8);
|
||||||
|
const pkey = new Uint8Array(algorithm.public);
|
||||||
|
const len = bytes > this._keyBytes ? bytes : this._keyBytes;
|
||||||
|
const secret = modPow(u8ArrayToBigInt(pkey), this._privateKeyBigInt, this._pBigInt);
|
||||||
|
return bigIntToU8Array(secret, len).slice(0, len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -7,12 +7,15 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Performs MD5 hashing on a string of binary characters, returns an array of bytes
|
* Performs MD5 hashing on an array of bytes, returns an array of bytes
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export function MD5(d) {
|
export async function MD5(d) {
|
||||||
let r = M(V(Y(X(d), 8 * d.length)));
|
let s = "";
|
||||||
return r;
|
for (let i = 0; i < d.length; i++) {
|
||||||
|
s += String.fromCharCode(d[i]);
|
||||||
|
}
|
||||||
|
return M(V(Y(X(s), 8 * s.length)));
|
||||||
}
|
}
|
||||||
|
|
||||||
function M(d) {
|
function M(d) {
|
||||||
|
|
@ -0,0 +1,132 @@
|
||||||
|
import Base64 from "../base64.js";
|
||||||
|
import { modPow, bigIntToU8Array, u8ArrayToBigInt } from "./bigint.js";
|
||||||
|
|
||||||
|
export class RSACipher {
|
||||||
|
constructor() {
|
||||||
|
this._keyLength = 0;
|
||||||
|
this._keyBytes = 0;
|
||||||
|
this._n = null;
|
||||||
|
this._e = null;
|
||||||
|
this._d = null;
|
||||||
|
this._nBigInt = null;
|
||||||
|
this._eBigInt = null;
|
||||||
|
this._dBigInt = null;
|
||||||
|
this._extractable = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
get algorithm() {
|
||||||
|
return { name: "RSA-PKCS1-v1_5" };
|
||||||
|
}
|
||||||
|
|
||||||
|
_base64urlDecode(data) {
|
||||||
|
data = data.replace(/-/g, "+").replace(/_/g, "/");
|
||||||
|
data = data.padEnd(Math.ceil(data.length / 4) * 4, "=");
|
||||||
|
return Base64.decode(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
_padArray(arr, length) {
|
||||||
|
const res = new Uint8Array(length);
|
||||||
|
res.set(arr, length - arr.length);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
static async generateKey(algorithm, extractable, _keyUsages) {
|
||||||
|
const cipher = new RSACipher;
|
||||||
|
await cipher._generateKey(algorithm, extractable);
|
||||||
|
return { privateKey: cipher };
|
||||||
|
}
|
||||||
|
|
||||||
|
async _generateKey(algorithm, extractable) {
|
||||||
|
this._keyLength = algorithm.modulusLength;
|
||||||
|
this._keyBytes = Math.ceil(this._keyLength / 8);
|
||||||
|
const key = await window.crypto.subtle.generateKey(
|
||||||
|
{
|
||||||
|
name: "RSA-OAEP",
|
||||||
|
modulusLength: algorithm.modulusLength,
|
||||||
|
publicExponent: algorithm.publicExponent,
|
||||||
|
hash: {name: "SHA-256"},
|
||||||
|
},
|
||||||
|
true, ["encrypt", "decrypt"]);
|
||||||
|
const privateKey = await window.crypto.subtle.exportKey("jwk", key.privateKey);
|
||||||
|
this._n = this._padArray(this._base64urlDecode(privateKey.n), this._keyBytes);
|
||||||
|
this._nBigInt = u8ArrayToBigInt(this._n);
|
||||||
|
this._e = this._padArray(this._base64urlDecode(privateKey.e), this._keyBytes);
|
||||||
|
this._eBigInt = u8ArrayToBigInt(this._e);
|
||||||
|
this._d = this._padArray(this._base64urlDecode(privateKey.d), this._keyBytes);
|
||||||
|
this._dBigInt = u8ArrayToBigInt(this._d);
|
||||||
|
this._extractable = extractable;
|
||||||
|
}
|
||||||
|
|
||||||
|
static async importKey(key, _algorithm, extractable, keyUsages) {
|
||||||
|
if (keyUsages.length !== 1 || keyUsages[0] !== "encrypt") {
|
||||||
|
throw new Error("only support importing RSA public key");
|
||||||
|
}
|
||||||
|
const cipher = new RSACipher;
|
||||||
|
await cipher._importKey(key, extractable);
|
||||||
|
return cipher;
|
||||||
|
}
|
||||||
|
|
||||||
|
async _importKey(key, extractable) {
|
||||||
|
const n = key.n;
|
||||||
|
const e = key.e;
|
||||||
|
if (n.length !== e.length) {
|
||||||
|
throw new Error("the sizes of modulus and public exponent do not match");
|
||||||
|
}
|
||||||
|
this._keyBytes = n.length;
|
||||||
|
this._keyLength = this._keyBytes * 8;
|
||||||
|
this._n = new Uint8Array(this._keyBytes);
|
||||||
|
this._e = new Uint8Array(this._keyBytes);
|
||||||
|
this._n.set(n);
|
||||||
|
this._e.set(e);
|
||||||
|
this._nBigInt = u8ArrayToBigInt(this._n);
|
||||||
|
this._eBigInt = u8ArrayToBigInt(this._e);
|
||||||
|
this._extractable = extractable;
|
||||||
|
}
|
||||||
|
|
||||||
|
async encrypt(_algorithm, message) {
|
||||||
|
if (message.length > this._keyBytes - 11) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const ps = new Uint8Array(this._keyBytes - message.length - 3);
|
||||||
|
window.crypto.getRandomValues(ps);
|
||||||
|
for (let i = 0; i < ps.length; i++) {
|
||||||
|
ps[i] = Math.floor(ps[i] * 254 / 255 + 1);
|
||||||
|
}
|
||||||
|
const em = new Uint8Array(this._keyBytes);
|
||||||
|
em[1] = 0x02;
|
||||||
|
em.set(ps, 2);
|
||||||
|
em.set(message, ps.length + 3);
|
||||||
|
const emBigInt = u8ArrayToBigInt(em);
|
||||||
|
const c = modPow(emBigInt, this._eBigInt, this._nBigInt);
|
||||||
|
return bigIntToU8Array(c, this._keyBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
async decrypt(_algorithm, message) {
|
||||||
|
if (message.length !== this._keyBytes) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const msgBigInt = u8ArrayToBigInt(message);
|
||||||
|
const emBigInt = modPow(msgBigInt, this._dBigInt, this._nBigInt);
|
||||||
|
const em = bigIntToU8Array(emBigInt, this._keyBytes);
|
||||||
|
if (em[0] !== 0x00 || em[1] !== 0x02) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
let i = 2;
|
||||||
|
for (; i < em.length; i++) {
|
||||||
|
if (em[i] === 0x00) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (i === em.length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return em.slice(i + 1, em.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
async exportKey() {
|
||||||
|
if (!this._extractable) {
|
||||||
|
throw new Error("key is not extractable");
|
||||||
|
}
|
||||||
|
return { n: this._n, e: this._e, d: this._d };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -31,10 +31,7 @@ export default class HextileDecoder {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let rQ = sock.rQ;
|
let subencoding = sock.rQpeek8();
|
||||||
let rQi = sock.rQi;
|
|
||||||
|
|
||||||
let subencoding = rQ[rQi]; // Peek
|
|
||||||
if (subencoding > 30) { // Raw
|
if (subencoding > 30) { // Raw
|
||||||
throw new Error("Illegal hextile subencoding (subencoding: " +
|
throw new Error("Illegal hextile subencoding (subencoding: " +
|
||||||
subencoding + ")");
|
subencoding + ")");
|
||||||
|
|
@ -65,7 +62,7 @@ export default class HextileDecoder {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let subrects = rQ[rQi + bytes - 1]; // Peek
|
let subrects = sock.rQpeekBytes(bytes).at(-1);
|
||||||
if (subencoding & 0x10) { // SubrectsColoured
|
if (subencoding & 0x10) { // SubrectsColoured
|
||||||
bytes += subrects * (4 + 2);
|
bytes += subrects * (4 + 2);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -79,7 +76,7 @@ export default class HextileDecoder {
|
||||||
}
|
}
|
||||||
|
|
||||||
// We know the encoding and have a whole tile
|
// We know the encoding and have a whole tile
|
||||||
rQi++;
|
sock.rQshift8();
|
||||||
if (subencoding === 0) {
|
if (subencoding === 0) {
|
||||||
if (this._lastsubencoding & 0x01) {
|
if (this._lastsubencoding & 0x01) {
|
||||||
// Weird: ignore blanks are RAW
|
// Weird: ignore blanks are RAW
|
||||||
|
|
@ -89,42 +86,36 @@ export default class HextileDecoder {
|
||||||
}
|
}
|
||||||
} else if (subencoding & 0x01) { // Raw
|
} else if (subencoding & 0x01) { // Raw
|
||||||
let pixels = tw * th;
|
let pixels = tw * th;
|
||||||
|
let data = sock.rQshiftBytes(pixels * 4, false);
|
||||||
// Max sure the image is fully opaque
|
// Max sure the image is fully opaque
|
||||||
for (let i = 0;i < pixels;i++) {
|
for (let i = 0;i < pixels;i++) {
|
||||||
rQ[rQi + i * 4 + 3] = 255;
|
data[i * 4 + 3] = 255;
|
||||||
}
|
}
|
||||||
display.blitImage(tx, ty, tw, th, rQ, rQi);
|
display.blitImage(tx, ty, tw, th, data, 0);
|
||||||
rQi += bytes - 1;
|
|
||||||
} else {
|
} else {
|
||||||
if (subencoding & 0x02) { // Background
|
if (subencoding & 0x02) { // Background
|
||||||
this._background = [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2], rQ[rQi + 3]];
|
this._background = new Uint8Array(sock.rQshiftBytes(4));
|
||||||
rQi += 4;
|
|
||||||
}
|
}
|
||||||
if (subencoding & 0x04) { // Foreground
|
if (subencoding & 0x04) { // Foreground
|
||||||
this._foreground = [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2], rQ[rQi + 3]];
|
this._foreground = new Uint8Array(sock.rQshiftBytes(4));
|
||||||
rQi += 4;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this._startTile(tx, ty, tw, th, this._background);
|
this._startTile(tx, ty, tw, th, this._background);
|
||||||
if (subencoding & 0x08) { // AnySubrects
|
if (subencoding & 0x08) { // AnySubrects
|
||||||
let subrects = rQ[rQi];
|
let subrects = sock.rQshift8();
|
||||||
rQi++;
|
|
||||||
|
|
||||||
for (let s = 0; s < subrects; s++) {
|
for (let s = 0; s < subrects; s++) {
|
||||||
let color;
|
let color;
|
||||||
if (subencoding & 0x10) { // SubrectsColoured
|
if (subencoding & 0x10) { // SubrectsColoured
|
||||||
color = [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2], rQ[rQi + 3]];
|
color = sock.rQshiftBytes(4);
|
||||||
rQi += 4;
|
|
||||||
} else {
|
} else {
|
||||||
color = this._foreground;
|
color = this._foreground;
|
||||||
}
|
}
|
||||||
const xy = rQ[rQi];
|
const xy = sock.rQshift8();
|
||||||
rQi++;
|
|
||||||
const sx = (xy >> 4);
|
const sx = (xy >> 4);
|
||||||
const sy = (xy & 0x0f);
|
const sy = (xy & 0x0f);
|
||||||
|
|
||||||
const wh = rQ[rQi];
|
const wh = sock.rQshift8();
|
||||||
rQi++;
|
|
||||||
const sw = (wh >> 4) + 1;
|
const sw = (wh >> 4) + 1;
|
||||||
const sh = (wh & 0x0f) + 1;
|
const sh = (wh & 0x0f) + 1;
|
||||||
|
|
||||||
|
|
@ -133,7 +124,6 @@ export default class HextileDecoder {
|
||||||
}
|
}
|
||||||
this._finishTile(display);
|
this._finishTile(display);
|
||||||
}
|
}
|
||||||
sock.rQi = rQi;
|
|
||||||
this._lastsubencoding = subencoding;
|
this._lastsubencoding = subencoding;
|
||||||
this._tiles--;
|
this._tiles--;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,131 +11,136 @@ export default class JPEGDecoder {
|
||||||
constructor() {
|
constructor() {
|
||||||
// RealVNC will reuse the quantization tables
|
// RealVNC will reuse the quantization tables
|
||||||
// and Huffman tables, so we need to cache them.
|
// and Huffman tables, so we need to cache them.
|
||||||
this._quantTables = [];
|
|
||||||
this._huffmanTables = [];
|
|
||||||
this._cachedQuantTables = [];
|
this._cachedQuantTables = [];
|
||||||
this._cachedHuffmanTables = [];
|
this._cachedHuffmanTables = [];
|
||||||
|
|
||||||
this._jpegLength = 0;
|
|
||||||
this._segments = [];
|
this._segments = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
decodeRect(x, y, width, height, sock, display, depth) {
|
decodeRect(x, y, width, height, sock, display, depth) {
|
||||||
// A rect of JPEG encodings is simply a JPEG file
|
// A rect of JPEG encodings is simply a JPEG file
|
||||||
if (!this._parseJPEG(sock.rQslice(0))) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const data = sock.rQshiftBytes(this._jpegLength);
|
|
||||||
if (this._quantTables.length != 0 && this._huffmanTables.length != 0) {
|
|
||||||
// If there are quantization tables and Huffman tables in the JPEG
|
|
||||||
// image, we can directly render it.
|
|
||||||
display.imageRect(x, y, width, height, "image/jpeg", data);
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
// Otherwise we need to insert cached tables.
|
|
||||||
const sofIndex = this._segments.findIndex(
|
|
||||||
x => x[1] == 0xC0 || x[1] == 0xC2
|
|
||||||
);
|
|
||||||
if (sofIndex == -1) {
|
|
||||||
throw new Error("Illegal JPEG image without SOF");
|
|
||||||
}
|
|
||||||
let segments = this._segments.slice(0, sofIndex);
|
|
||||||
segments = segments.concat(this._quantTables.length ?
|
|
||||||
this._quantTables :
|
|
||||||
this._cachedQuantTables);
|
|
||||||
segments.push(this._segments[sofIndex]);
|
|
||||||
segments = segments.concat(this._huffmanTables.length ?
|
|
||||||
this._huffmanTables :
|
|
||||||
this._cachedHuffmanTables,
|
|
||||||
this._segments.slice(sofIndex + 1));
|
|
||||||
let length = 0;
|
|
||||||
for (let i = 0; i < segments.length; i++) {
|
|
||||||
length += segments[i].length;
|
|
||||||
}
|
|
||||||
const data = new Uint8Array(length);
|
|
||||||
length = 0;
|
|
||||||
for (let i = 0; i < segments.length; i++) {
|
|
||||||
data.set(segments[i], length);
|
|
||||||
length += segments[i].length;
|
|
||||||
}
|
|
||||||
display.imageRect(x, y, width, height, "image/jpeg", data);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_parseJPEG(buffer) {
|
|
||||||
if (this._quantTables.length != 0) {
|
|
||||||
this._cachedQuantTables = this._quantTables;
|
|
||||||
}
|
|
||||||
if (this._huffmanTables.length != 0) {
|
|
||||||
this._cachedHuffmanTables = this._huffmanTables;
|
|
||||||
}
|
|
||||||
this._quantTables = [];
|
|
||||||
this._huffmanTables = [];
|
|
||||||
this._segments = [];
|
|
||||||
let i = 0;
|
|
||||||
let bufferLength = buffer.length;
|
|
||||||
while (true) {
|
while (true) {
|
||||||
let j = i;
|
let segment = this._readSegment(sock);
|
||||||
if (j + 2 > bufferLength) {
|
if (segment === null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (buffer[j] != 0xFF) {
|
|
||||||
throw new Error("Illegal JPEG marker received (byte: " +
|
|
||||||
buffer[j] + ")");
|
|
||||||
}
|
|
||||||
const type = buffer[j+1];
|
|
||||||
j += 2;
|
|
||||||
if (type == 0xD9) {
|
|
||||||
this._jpegLength = j;
|
|
||||||
this._segments.push(buffer.slice(i, j));
|
|
||||||
return true;
|
|
||||||
} else if (type == 0xDA) {
|
|
||||||
// start of scan
|
|
||||||
let hasFoundEndOfScan = false;
|
|
||||||
for (let k = j + 3; k + 1 < bufferLength; k++) {
|
|
||||||
if (buffer[k] == 0xFF && buffer[k+1] != 0x00 &&
|
|
||||||
!(buffer[k+1] >= 0xD0 && buffer[k+1] <= 0xD7)) {
|
|
||||||
j = k;
|
|
||||||
hasFoundEndOfScan = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!hasFoundEndOfScan) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
this._segments.push(buffer.slice(i, j));
|
|
||||||
i = j;
|
|
||||||
continue;
|
|
||||||
} else if (type >= 0xD0 && type < 0xD9 || type == 0x01) {
|
|
||||||
// No length after marker
|
|
||||||
this._segments.push(buffer.slice(i, j));
|
|
||||||
i = j;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (j + 2 > bufferLength) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const length = (buffer[j] << 8) + buffer[j+1] - 2;
|
|
||||||
if (length < 0) {
|
|
||||||
throw new Error("Illegal JPEG length received (length: " +
|
|
||||||
length + ")");
|
|
||||||
}
|
|
||||||
j += 2;
|
|
||||||
if (j + length > bufferLength) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
j += length;
|
|
||||||
const segment = buffer.slice(i, j);
|
|
||||||
if (type == 0xC4) {
|
|
||||||
// Huffman tables
|
|
||||||
this._huffmanTables.push(segment);
|
|
||||||
} else if (type == 0xDB) {
|
|
||||||
// Quantization tables
|
|
||||||
this._quantTables.push(segment);
|
|
||||||
}
|
|
||||||
this._segments.push(segment);
|
this._segments.push(segment);
|
||||||
i = j;
|
// End of image?
|
||||||
|
if (segment[1] === 0xD9) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let huffmanTables = [];
|
||||||
|
let quantTables = [];
|
||||||
|
for (let segment of this._segments) {
|
||||||
|
let type = segment[1];
|
||||||
|
if (type === 0xC4) {
|
||||||
|
// Huffman tables
|
||||||
|
huffmanTables.push(segment);
|
||||||
|
} else if (type === 0xDB) {
|
||||||
|
// Quantization tables
|
||||||
|
quantTables.push(segment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const sofIndex = this._segments.findIndex(
|
||||||
|
x => x[1] == 0xC0 || x[1] == 0xC2
|
||||||
|
);
|
||||||
|
if (sofIndex == -1) {
|
||||||
|
throw new Error("Illegal JPEG image without SOF");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (quantTables.length === 0) {
|
||||||
|
this._segments.splice(sofIndex+1, 0,
|
||||||
|
...this._cachedQuantTables);
|
||||||
|
}
|
||||||
|
if (huffmanTables.length === 0) {
|
||||||
|
this._segments.splice(sofIndex+1, 0,
|
||||||
|
...this._cachedHuffmanTables);
|
||||||
|
}
|
||||||
|
|
||||||
|
let length = 0;
|
||||||
|
for (let segment of this._segments) {
|
||||||
|
length += segment.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
let data = new Uint8Array(length);
|
||||||
|
length = 0;
|
||||||
|
for (let segment of this._segments) {
|
||||||
|
data.set(segment, length);
|
||||||
|
length += segment.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
display.imageRect(x, y, width, height, "image/jpeg", data);
|
||||||
|
|
||||||
|
if (huffmanTables.length !== 0) {
|
||||||
|
this._cachedHuffmanTables = huffmanTables;
|
||||||
|
}
|
||||||
|
if (quantTables.length !== 0) {
|
||||||
|
this._cachedQuantTables = quantTables;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._segments = [];
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
_readSegment(sock) {
|
||||||
|
if (sock.rQwait("JPEG", 2)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let marker = sock.rQshift8();
|
||||||
|
if (marker != 0xFF) {
|
||||||
|
throw new Error("Illegal JPEG marker received (byte: " +
|
||||||
|
marker + ")");
|
||||||
|
}
|
||||||
|
let type = sock.rQshift8();
|
||||||
|
if (type >= 0xD0 && type <= 0xD9 || type == 0x01) {
|
||||||
|
// No length after marker
|
||||||
|
return new Uint8Array([marker, type]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sock.rQwait("JPEG", 2, 2)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let length = sock.rQshift16();
|
||||||
|
if (length < 2) {
|
||||||
|
throw new Error("Illegal JPEG length received (length: " +
|
||||||
|
length + ")");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sock.rQwait("JPEG", length-2, 4)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let extra = 0;
|
||||||
|
if (type === 0xDA) {
|
||||||
|
// start of scan
|
||||||
|
extra += 2;
|
||||||
|
while (true) {
|
||||||
|
if (sock.rQwait("JPEG", length-2+extra, 4)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
let data = sock.rQpeekBytes(length-2+extra, false);
|
||||||
|
if (data.at(-2) === 0xFF && data.at(-1) !== 0x00 &&
|
||||||
|
!(data.at(-1) >= 0xD0 && data.at(-1) <= 0xD7)) {
|
||||||
|
extra -= 2;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
extra++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let segment = new Uint8Array(2 + length + extra);
|
||||||
|
segment[0] = marker;
|
||||||
|
segment[1] = type;
|
||||||
|
segment[2] = length >> 8;
|
||||||
|
segment[3] = length;
|
||||||
|
segment.set(sock.rQshiftBytes(length-2+extra, false), 4);
|
||||||
|
|
||||||
|
return segment;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,41 +24,34 @@ export default class RawDecoder {
|
||||||
const pixelSize = depth == 8 ? 1 : 4;
|
const pixelSize = depth == 8 ? 1 : 4;
|
||||||
const bytesPerLine = width * pixelSize;
|
const bytesPerLine = width * pixelSize;
|
||||||
|
|
||||||
if (sock.rQwait("RAW", bytesPerLine)) {
|
while (this._lines > 0) {
|
||||||
return false;
|
if (sock.rQwait("RAW", bytesPerLine)) {
|
||||||
}
|
return false;
|
||||||
|
|
||||||
const curY = y + (height - this._lines);
|
|
||||||
const currHeight = Math.min(this._lines,
|
|
||||||
Math.floor(sock.rQlen / bytesPerLine));
|
|
||||||
const pixels = width * currHeight;
|
|
||||||
|
|
||||||
let data = sock.rQ;
|
|
||||||
let index = sock.rQi;
|
|
||||||
|
|
||||||
// Convert data if needed
|
|
||||||
if (depth == 8) {
|
|
||||||
const newdata = new Uint8Array(pixels * 4);
|
|
||||||
for (let i = 0; i < pixels; i++) {
|
|
||||||
newdata[i * 4 + 0] = ((data[index + i] >> 0) & 0x3) * 255 / 3;
|
|
||||||
newdata[i * 4 + 1] = ((data[index + i] >> 2) & 0x3) * 255 / 3;
|
|
||||||
newdata[i * 4 + 2] = ((data[index + i] >> 4) & 0x3) * 255 / 3;
|
|
||||||
newdata[i * 4 + 3] = 255;
|
|
||||||
}
|
}
|
||||||
data = newdata;
|
|
||||||
index = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Max sure the image is fully opaque
|
const curY = y + (height - this._lines);
|
||||||
for (let i = 0; i < pixels; i++) {
|
|
||||||
data[index + i * 4 + 3] = 255;
|
|
||||||
}
|
|
||||||
|
|
||||||
display.blitImage(x, curY, width, currHeight, data, index);
|
let data = sock.rQshiftBytes(bytesPerLine, false);
|
||||||
sock.rQskipBytes(currHeight * bytesPerLine);
|
|
||||||
this._lines -= currHeight;
|
// Convert data if needed
|
||||||
if (this._lines > 0) {
|
if (depth == 8) {
|
||||||
return false;
|
const newdata = new Uint8Array(width * 4);
|
||||||
|
for (let i = 0; i < width; i++) {
|
||||||
|
newdata[i * 4 + 0] = ((data[i] >> 0) & 0x3) * 255 / 3;
|
||||||
|
newdata[i * 4 + 1] = ((data[i] >> 2) & 0x3) * 255 / 3;
|
||||||
|
newdata[i * 4 + 2] = ((data[i] >> 4) & 0x3) * 255 / 3;
|
||||||
|
newdata[i * 4 + 3] = 255;
|
||||||
|
}
|
||||||
|
data = newdata;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Max sure the image is fully opaque
|
||||||
|
for (let i = 0; i < width; i++) {
|
||||||
|
data[i * 4 + 3] = 255;
|
||||||
|
}
|
||||||
|
|
||||||
|
display.blitImage(x, curY, width, 1, data, 0);
|
||||||
|
this._lines--;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
|
||||||
|
|
@ -76,12 +76,8 @@ export default class TightDecoder {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const rQi = sock.rQi;
|
let pixel = sock.rQshiftBytes(3);
|
||||||
const rQ = sock.rQ;
|
display.fillRect(x, y, width, height, pixel, false);
|
||||||
|
|
||||||
display.fillRect(x, y, width, height,
|
|
||||||
[rQ[rQi], rQ[rQi + 1], rQ[rQi + 2]], false);
|
|
||||||
sock.rQskipBytes(3);
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -316,7 +312,7 @@ export default class TightDecoder {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
let data = sock.rQshiftBytes(this._len);
|
let data = sock.rQshiftBytes(this._len, false);
|
||||||
this._len = 0;
|
this._len = 0;
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ export default class ZRLEDecoder {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = sock.rQshiftBytes(this._length);
|
const data = sock.rQshiftBytes(this._length, false);
|
||||||
|
|
||||||
this._inflator.setInput(data);
|
this._inflator.setInput(data);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ export default class Display {
|
||||||
this._drawCtx = null;
|
this._drawCtx = null;
|
||||||
|
|
||||||
this._renderQ = []; // queue drawing actions for in-oder rendering
|
this._renderQ = []; // queue drawing actions for in-oder rendering
|
||||||
this._flushing = false;
|
this._flushPromise = null;
|
||||||
|
|
||||||
// the full frame buffer (logical canvas) size
|
// the full frame buffer (logical canvas) size
|
||||||
this._fbWidth = 0;
|
this._fbWidth = 0;
|
||||||
|
|
@ -61,10 +61,6 @@ export default class Display {
|
||||||
|
|
||||||
this._scale = 1.0;
|
this._scale = 1.0;
|
||||||
this._clipViewport = false;
|
this._clipViewport = false;
|
||||||
|
|
||||||
// ===== EVENT HANDLERS =====
|
|
||||||
|
|
||||||
this.onflush = () => {}; // A flush request has finished
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== PROPERTIES =====
|
// ===== PROPERTIES =====
|
||||||
|
|
@ -306,9 +302,14 @@ export default class Display {
|
||||||
|
|
||||||
flush() {
|
flush() {
|
||||||
if (this._renderQ.length === 0) {
|
if (this._renderQ.length === 0) {
|
||||||
this.onflush();
|
return Promise.resolve();
|
||||||
} else {
|
} else {
|
||||||
this._flushing = true;
|
if (this._flushPromise === null) {
|
||||||
|
this._flushPromise = new Promise((resolve) => {
|
||||||
|
this._flushResolve = resolve;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return this._flushPromise;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -517,9 +518,11 @@ export default class Display {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this._renderQ.length === 0 && this._flushing) {
|
if (this._renderQ.length === 0 &&
|
||||||
this._flushing = false;
|
this._flushPromise !== null) {
|
||||||
this.onflush();
|
this._flushResolve();
|
||||||
|
this._flushPromise = null;
|
||||||
|
this._flushResolve = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
345
core/ra2.js
345
core/ra2.js
|
|
@ -1,146 +1,25 @@
|
||||||
import Base64 from './base64.js';
|
|
||||||
import { encodeUTF8 } from './util/strings.js';
|
import { encodeUTF8 } from './util/strings.js';
|
||||||
import EventTargetMixin from './util/eventtarget.js';
|
import EventTargetMixin from './util/eventtarget.js';
|
||||||
|
import legacyCrypto from './crypto/crypto.js';
|
||||||
|
|
||||||
export class AESEAXCipher {
|
class RA2Cipher {
|
||||||
constructor() {
|
constructor() {
|
||||||
this._rawKey = null;
|
this._cipher = null;
|
||||||
this._ctrKey = null;
|
|
||||||
this._cbcKey = null;
|
|
||||||
this._zeroBlock = new Uint8Array(16);
|
|
||||||
this._prefixBlock0 = this._zeroBlock;
|
|
||||||
this._prefixBlock1 = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]);
|
|
||||||
this._prefixBlock2 = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2]);
|
|
||||||
}
|
|
||||||
|
|
||||||
async _encryptBlock(block) {
|
|
||||||
const encrypted = await window.crypto.subtle.encrypt({
|
|
||||||
name: "AES-CBC",
|
|
||||||
iv: this._zeroBlock,
|
|
||||||
}, this._cbcKey, block);
|
|
||||||
return new Uint8Array(encrypted).slice(0, 16);
|
|
||||||
}
|
|
||||||
|
|
||||||
async _initCMAC() {
|
|
||||||
const k1 = await this._encryptBlock(this._zeroBlock);
|
|
||||||
const k2 = new Uint8Array(16);
|
|
||||||
const v = k1[0] >>> 6;
|
|
||||||
for (let i = 0; i < 15; i++) {
|
|
||||||
k2[i] = (k1[i + 1] >> 6) | (k1[i] << 2);
|
|
||||||
k1[i] = (k1[i + 1] >> 7) | (k1[i] << 1);
|
|
||||||
}
|
|
||||||
const lut = [0x0, 0x87, 0x0e, 0x89];
|
|
||||||
k2[14] ^= v >>> 1;
|
|
||||||
k2[15] = (k1[15] << 2) ^ lut[v];
|
|
||||||
k1[15] = (k1[15] << 1) ^ lut[v >> 1];
|
|
||||||
this._k1 = k1;
|
|
||||||
this._k2 = k2;
|
|
||||||
}
|
|
||||||
|
|
||||||
async _encryptCTR(data, counter) {
|
|
||||||
const encrypted = await window.crypto.subtle.encrypt({
|
|
||||||
"name": "AES-CTR",
|
|
||||||
counter: counter,
|
|
||||||
length: 128
|
|
||||||
}, this._ctrKey, data);
|
|
||||||
return new Uint8Array(encrypted);
|
|
||||||
}
|
|
||||||
|
|
||||||
async _decryptCTR(data, counter) {
|
|
||||||
const decrypted = await window.crypto.subtle.decrypt({
|
|
||||||
"name": "AES-CTR",
|
|
||||||
counter: counter,
|
|
||||||
length: 128
|
|
||||||
}, this._ctrKey, data);
|
|
||||||
return new Uint8Array(decrypted);
|
|
||||||
}
|
|
||||||
|
|
||||||
async _computeCMAC(data, prefixBlock) {
|
|
||||||
if (prefixBlock.length !== 16) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const n = Math.floor(data.length / 16);
|
|
||||||
const m = Math.ceil(data.length / 16);
|
|
||||||
const r = data.length - n * 16;
|
|
||||||
const cbcData = new Uint8Array((m + 1) * 16);
|
|
||||||
cbcData.set(prefixBlock);
|
|
||||||
cbcData.set(data, 16);
|
|
||||||
if (r === 0) {
|
|
||||||
for (let i = 0; i < 16; i++) {
|
|
||||||
cbcData[n * 16 + i] ^= this._k1[i];
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
cbcData[(n + 1) * 16 + r] = 0x80;
|
|
||||||
for (let i = 0; i < 16; i++) {
|
|
||||||
cbcData[(n + 1) * 16 + i] ^= this._k2[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let cbcEncrypted = await window.crypto.subtle.encrypt({
|
|
||||||
name: "AES-CBC",
|
|
||||||
iv: this._zeroBlock,
|
|
||||||
}, this._cbcKey, cbcData);
|
|
||||||
|
|
||||||
cbcEncrypted = new Uint8Array(cbcEncrypted);
|
|
||||||
const mac = cbcEncrypted.slice(cbcEncrypted.length - 32, cbcEncrypted.length - 16);
|
|
||||||
return mac;
|
|
||||||
}
|
|
||||||
|
|
||||||
async setKey(key) {
|
|
||||||
this._rawKey = key;
|
|
||||||
this._ctrKey = await window.crypto.subtle.importKey(
|
|
||||||
"raw", key, {"name": "AES-CTR"}, false, ["encrypt", "decrypt"]);
|
|
||||||
this._cbcKey = await window.crypto.subtle.importKey(
|
|
||||||
"raw", key, {"name": "AES-CBC"}, false, ["encrypt", "decrypt"]);
|
|
||||||
await this._initCMAC();
|
|
||||||
}
|
|
||||||
|
|
||||||
async encrypt(message, associatedData, nonce) {
|
|
||||||
const nCMAC = await this._computeCMAC(nonce, this._prefixBlock0);
|
|
||||||
const encrypted = await this._encryptCTR(message, nCMAC);
|
|
||||||
const adCMAC = await this._computeCMAC(associatedData, this._prefixBlock1);
|
|
||||||
const mac = await this._computeCMAC(encrypted, this._prefixBlock2);
|
|
||||||
for (let i = 0; i < 16; i++) {
|
|
||||||
mac[i] ^= nCMAC[i] ^ adCMAC[i];
|
|
||||||
}
|
|
||||||
const res = new Uint8Array(16 + encrypted.length);
|
|
||||||
res.set(encrypted);
|
|
||||||
res.set(mac, encrypted.length);
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
async decrypt(encrypted, associatedData, nonce, mac) {
|
|
||||||
const nCMAC = await this._computeCMAC(nonce, this._prefixBlock0);
|
|
||||||
const adCMAC = await this._computeCMAC(associatedData, this._prefixBlock1);
|
|
||||||
const computedMac = await this._computeCMAC(encrypted, this._prefixBlock2);
|
|
||||||
for (let i = 0; i < 16; i++) {
|
|
||||||
computedMac[i] ^= nCMAC[i] ^ adCMAC[i];
|
|
||||||
}
|
|
||||||
if (computedMac.length !== mac.length) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
for (let i = 0; i < mac.length; i++) {
|
|
||||||
if (computedMac[i] !== mac[i]) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const res = await this._decryptCTR(encrypted, nCMAC);
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class RA2Cipher {
|
|
||||||
constructor() {
|
|
||||||
this._cipher = new AESEAXCipher();
|
|
||||||
this._counter = new Uint8Array(16);
|
this._counter = new Uint8Array(16);
|
||||||
}
|
}
|
||||||
|
|
||||||
async setKey(key) {
|
async setKey(key) {
|
||||||
await this._cipher.setKey(key);
|
this._cipher = await legacyCrypto.importKey(
|
||||||
|
"raw", key, { name: "AES-EAX" }, false, ["encrypt, decrypt"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
async makeMessage(message) {
|
async makeMessage(message) {
|
||||||
const ad = new Uint8Array([(message.length & 0xff00) >>> 8, message.length & 0xff]);
|
const ad = new Uint8Array([(message.length & 0xff00) >>> 8, message.length & 0xff]);
|
||||||
const encrypted = await this._cipher.encrypt(message, ad, this._counter);
|
const encrypted = await legacyCrypto.encrypt({
|
||||||
|
name: "AES-EAX",
|
||||||
|
iv: this._counter,
|
||||||
|
additionalData: ad,
|
||||||
|
}, this._cipher, message);
|
||||||
for (let i = 0; i < 16 && this._counter[i]++ === 255; i++);
|
for (let i = 0; i < 16 && this._counter[i]++ === 255; i++);
|
||||||
const res = new Uint8Array(message.length + 2 + 16);
|
const res = new Uint8Array(message.length + 2 + 16);
|
||||||
res.set(ad);
|
res.set(ad);
|
||||||
|
|
@ -148,164 +27,18 @@ export class RA2Cipher {
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
async receiveMessage(length, encrypted, mac) {
|
async receiveMessage(length, encrypted) {
|
||||||
const ad = new Uint8Array([(length & 0xff00) >>> 8, length & 0xff]);
|
const ad = new Uint8Array([(length & 0xff00) >>> 8, length & 0xff]);
|
||||||
const res = await this._cipher.decrypt(encrypted, ad, this._counter, mac);
|
const res = await legacyCrypto.decrypt({
|
||||||
|
name: "AES-EAX",
|
||||||
|
iv: this._counter,
|
||||||
|
additionalData: ad,
|
||||||
|
}, this._cipher, encrypted);
|
||||||
for (let i = 0; i < 16 && this._counter[i]++ === 255; i++);
|
for (let i = 0; i < 16 && this._counter[i]++ === 255; i++);
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class RSACipher {
|
|
||||||
constructor(keyLength) {
|
|
||||||
this._key = null;
|
|
||||||
this._keyLength = keyLength;
|
|
||||||
this._keyBytes = Math.ceil(keyLength / 8);
|
|
||||||
this._n = null;
|
|
||||||
this._e = null;
|
|
||||||
this._d = null;
|
|
||||||
this._nBigInt = null;
|
|
||||||
this._eBigInt = null;
|
|
||||||
this._dBigInt = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
_base64urlDecode(data) {
|
|
||||||
data = data.replace(/-/g, "+").replace(/_/g, "/");
|
|
||||||
data = data.padEnd(Math.ceil(data.length / 4) * 4, "=");
|
|
||||||
return Base64.decode(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
_u8ArrayToBigInt(arr) {
|
|
||||||
let hex = '0x';
|
|
||||||
for (let i = 0; i < arr.length; i++) {
|
|
||||||
hex += arr[i].toString(16).padStart(2, '0');
|
|
||||||
}
|
|
||||||
return BigInt(hex);
|
|
||||||
}
|
|
||||||
|
|
||||||
_padArray(arr, length) {
|
|
||||||
const res = new Uint8Array(length);
|
|
||||||
res.set(arr, length - arr.length);
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
_bigIntToU8Array(bigint, padLength=0) {
|
|
||||||
let hex = bigint.toString(16);
|
|
||||||
if (padLength === 0) {
|
|
||||||
padLength = Math.ceil(hex.length / 2) * 2;
|
|
||||||
}
|
|
||||||
hex = hex.padStart(padLength * 2, '0');
|
|
||||||
const length = hex.length / 2;
|
|
||||||
const arr = new Uint8Array(length);
|
|
||||||
for (let i = 0; i < length; i++) {
|
|
||||||
arr[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
||||||
}
|
|
||||||
return arr;
|
|
||||||
}
|
|
||||||
|
|
||||||
_modPow(b, e, m) {
|
|
||||||
if (m === 1n) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
let r = 1n;
|
|
||||||
b = b % m;
|
|
||||||
while (e > 0) {
|
|
||||||
if (e % 2n === 1n) {
|
|
||||||
r = (r * b) % m;
|
|
||||||
}
|
|
||||||
e = e / 2n;
|
|
||||||
b = (b * b) % m;
|
|
||||||
}
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
|
|
||||||
async generateKey() {
|
|
||||||
this._key = await window.crypto.subtle.generateKey(
|
|
||||||
{
|
|
||||||
name: "RSA-OAEP",
|
|
||||||
modulusLength: this._keyLength,
|
|
||||||
publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
|
|
||||||
hash: {name: "SHA-256"},
|
|
||||||
},
|
|
||||||
true, ["encrypt", "decrypt"]);
|
|
||||||
const privateKey = await window.crypto.subtle.exportKey("jwk", this._key.privateKey);
|
|
||||||
this._n = this._padArray(this._base64urlDecode(privateKey.n), this._keyBytes);
|
|
||||||
this._nBigInt = this._u8ArrayToBigInt(this._n);
|
|
||||||
this._e = this._padArray(this._base64urlDecode(privateKey.e), this._keyBytes);
|
|
||||||
this._eBigInt = this._u8ArrayToBigInt(this._e);
|
|
||||||
this._d = this._padArray(this._base64urlDecode(privateKey.d), this._keyBytes);
|
|
||||||
this._dBigInt = this._u8ArrayToBigInt(this._d);
|
|
||||||
}
|
|
||||||
|
|
||||||
setPublicKey(n, e) {
|
|
||||||
if (n.length !== this._keyBytes || e.length !== this._keyBytes) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this._n = new Uint8Array(this._keyBytes);
|
|
||||||
this._e = new Uint8Array(this._keyBytes);
|
|
||||||
this._n.set(n);
|
|
||||||
this._e.set(e);
|
|
||||||
this._nBigInt = this._u8ArrayToBigInt(this._n);
|
|
||||||
this._eBigInt = this._u8ArrayToBigInt(this._e);
|
|
||||||
}
|
|
||||||
|
|
||||||
encrypt(message) {
|
|
||||||
if (message.length > this._keyBytes - 11) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const ps = new Uint8Array(this._keyBytes - message.length - 3);
|
|
||||||
window.crypto.getRandomValues(ps);
|
|
||||||
for (let i = 0; i < ps.length; i++) {
|
|
||||||
ps[i] = Math.floor(ps[i] * 254 / 255 + 1);
|
|
||||||
}
|
|
||||||
const em = new Uint8Array(this._keyBytes);
|
|
||||||
em[1] = 0x02;
|
|
||||||
em.set(ps, 2);
|
|
||||||
em.set(message, ps.length + 3);
|
|
||||||
const emBigInt = this._u8ArrayToBigInt(em);
|
|
||||||
const c = this._modPow(emBigInt, this._eBigInt, this._nBigInt);
|
|
||||||
return this._bigIntToU8Array(c, this._keyBytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
decrypt(message) {
|
|
||||||
if (message.length !== this._keyBytes) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const msgBigInt = this._u8ArrayToBigInt(message);
|
|
||||||
const emBigInt = this._modPow(msgBigInt, this._dBigInt, this._nBigInt);
|
|
||||||
const em = this._bigIntToU8Array(emBigInt, this._keyBytes);
|
|
||||||
if (em[0] !== 0x00 || em[1] !== 0x02) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
let i = 2;
|
|
||||||
for (; i < em.length; i++) {
|
|
||||||
if (em[i] === 0x00) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (i === em.length) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return em.slice(i + 1, em.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
get keyLength() {
|
|
||||||
return this._keyLength;
|
|
||||||
}
|
|
||||||
|
|
||||||
get n() {
|
|
||||||
return this._n;
|
|
||||||
}
|
|
||||||
|
|
||||||
get e() {
|
|
||||||
return this._e;
|
|
||||||
}
|
|
||||||
|
|
||||||
get d() {
|
|
||||||
return this._d;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class RSAAESAuthenticationState extends EventTargetMixin {
|
export default class RSAAESAuthenticationState extends EventTargetMixin {
|
||||||
constructor(sock, getCredentials) {
|
constructor(sock, getCredentials) {
|
||||||
super();
|
super();
|
||||||
|
|
@ -406,7 +139,7 @@ export default class RSAAESAuthenticationState extends EventTargetMixin {
|
||||||
this._hasStarted = true;
|
this._hasStarted = true;
|
||||||
// 1: Receive server public key
|
// 1: Receive server public key
|
||||||
await this._waitSockAsync(4);
|
await this._waitSockAsync(4);
|
||||||
const serverKeyLengthBuffer = this._sock.rQslice(0, 4);
|
const serverKeyLengthBuffer = this._sock.rQpeekBytes(4);
|
||||||
const serverKeyLength = this._sock.rQshift32();
|
const serverKeyLength = this._sock.rQshift32();
|
||||||
if (serverKeyLength < 1024) {
|
if (serverKeyLength < 1024) {
|
||||||
throw new Error("RA2: server public key is too short: " + serverKeyLength);
|
throw new Error("RA2: server public key is too short: " + serverKeyLength);
|
||||||
|
|
@ -417,26 +150,31 @@ export default class RSAAESAuthenticationState extends EventTargetMixin {
|
||||||
await this._waitSockAsync(serverKeyBytes * 2);
|
await this._waitSockAsync(serverKeyBytes * 2);
|
||||||
const serverN = this._sock.rQshiftBytes(serverKeyBytes);
|
const serverN = this._sock.rQshiftBytes(serverKeyBytes);
|
||||||
const serverE = this._sock.rQshiftBytes(serverKeyBytes);
|
const serverE = this._sock.rQshiftBytes(serverKeyBytes);
|
||||||
const serverRSACipher = new RSACipher(serverKeyLength);
|
const serverRSACipher = await legacyCrypto.importKey(
|
||||||
serverRSACipher.setPublicKey(serverN, serverE);
|
"raw", { n: serverN, e: serverE }, { name: "RSA-PKCS1-v1_5" }, false, ["encrypt"]);
|
||||||
const serverPublickey = new Uint8Array(4 + serverKeyBytes * 2);
|
const serverPublickey = new Uint8Array(4 + serverKeyBytes * 2);
|
||||||
serverPublickey.set(serverKeyLengthBuffer);
|
serverPublickey.set(serverKeyLengthBuffer);
|
||||||
serverPublickey.set(serverN, 4);
|
serverPublickey.set(serverN, 4);
|
||||||
serverPublickey.set(serverE, 4 + serverKeyBytes);
|
serverPublickey.set(serverE, 4 + serverKeyBytes);
|
||||||
|
|
||||||
// verify server public key
|
// verify server public key
|
||||||
|
let approveKey = this._waitApproveKeyAsync();
|
||||||
this.dispatchEvent(new CustomEvent("serververification", {
|
this.dispatchEvent(new CustomEvent("serververification", {
|
||||||
detail: { type: "RSA", publickey: serverPublickey }
|
detail: { type: "RSA", publickey: serverPublickey }
|
||||||
}));
|
}));
|
||||||
await this._waitApproveKeyAsync();
|
await approveKey;
|
||||||
|
|
||||||
// 2: Send client public key
|
// 2: Send client public key
|
||||||
const clientKeyLength = 2048;
|
const clientKeyLength = 2048;
|
||||||
const clientKeyBytes = Math.ceil(clientKeyLength / 8);
|
const clientKeyBytes = Math.ceil(clientKeyLength / 8);
|
||||||
const clientRSACipher = new RSACipher(clientKeyLength);
|
const clientRSACipher = (await legacyCrypto.generateKey({
|
||||||
await clientRSACipher.generateKey();
|
name: "RSA-PKCS1-v1_5",
|
||||||
const clientN = clientRSACipher.n;
|
modulusLength: clientKeyLength,
|
||||||
const clientE = clientRSACipher.e;
|
publicExponent: new Uint8Array([1, 0, 1]),
|
||||||
|
}, true, ["encrypt"])).privateKey;
|
||||||
|
const clientExportedRSAKey = await legacyCrypto.exportKey("raw", clientRSACipher);
|
||||||
|
const clientN = clientExportedRSAKey.n;
|
||||||
|
const clientE = clientExportedRSAKey.e;
|
||||||
const clientPublicKey = new Uint8Array(4 + clientKeyBytes * 2);
|
const clientPublicKey = new Uint8Array(4 + clientKeyBytes * 2);
|
||||||
clientPublicKey[0] = (clientKeyLength & 0xff000000) >>> 24;
|
clientPublicKey[0] = (clientKeyLength & 0xff000000) >>> 24;
|
||||||
clientPublicKey[1] = (clientKeyLength & 0xff0000) >>> 16;
|
clientPublicKey[1] = (clientKeyLength & 0xff0000) >>> 16;
|
||||||
|
|
@ -444,17 +182,20 @@ export default class RSAAESAuthenticationState extends EventTargetMixin {
|
||||||
clientPublicKey[3] = clientKeyLength & 0xff;
|
clientPublicKey[3] = clientKeyLength & 0xff;
|
||||||
clientPublicKey.set(clientN, 4);
|
clientPublicKey.set(clientN, 4);
|
||||||
clientPublicKey.set(clientE, 4 + clientKeyBytes);
|
clientPublicKey.set(clientE, 4 + clientKeyBytes);
|
||||||
this._sock.send(clientPublicKey);
|
this._sock.sQpushBytes(clientPublicKey);
|
||||||
|
this._sock.flush();
|
||||||
|
|
||||||
// 3: Send client random
|
// 3: Send client random
|
||||||
const clientRandom = new Uint8Array(16);
|
const clientRandom = new Uint8Array(16);
|
||||||
window.crypto.getRandomValues(clientRandom);
|
window.crypto.getRandomValues(clientRandom);
|
||||||
const clientEncryptedRandom = serverRSACipher.encrypt(clientRandom);
|
const clientEncryptedRandom = await legacyCrypto.encrypt(
|
||||||
|
{ name: "RSA-PKCS1-v1_5" }, serverRSACipher, clientRandom);
|
||||||
const clientRandomMessage = new Uint8Array(2 + serverKeyBytes);
|
const clientRandomMessage = new Uint8Array(2 + serverKeyBytes);
|
||||||
clientRandomMessage[0] = (serverKeyBytes & 0xff00) >>> 8;
|
clientRandomMessage[0] = (serverKeyBytes & 0xff00) >>> 8;
|
||||||
clientRandomMessage[1] = serverKeyBytes & 0xff;
|
clientRandomMessage[1] = serverKeyBytes & 0xff;
|
||||||
clientRandomMessage.set(clientEncryptedRandom, 2);
|
clientRandomMessage.set(clientEncryptedRandom, 2);
|
||||||
this._sock.send(clientRandomMessage);
|
this._sock.sQpushBytes(clientRandomMessage);
|
||||||
|
this._sock.flush();
|
||||||
|
|
||||||
// 4: Receive server random
|
// 4: Receive server random
|
||||||
await this._waitSockAsync(2);
|
await this._waitSockAsync(2);
|
||||||
|
|
@ -462,7 +203,8 @@ export default class RSAAESAuthenticationState extends EventTargetMixin {
|
||||||
throw new Error("RA2: wrong encrypted message length");
|
throw new Error("RA2: wrong encrypted message length");
|
||||||
}
|
}
|
||||||
const serverEncryptedRandom = this._sock.rQshiftBytes(clientKeyBytes);
|
const serverEncryptedRandom = this._sock.rQshiftBytes(clientKeyBytes);
|
||||||
const serverRandom = clientRSACipher.decrypt(serverEncryptedRandom);
|
const serverRandom = await legacyCrypto.decrypt(
|
||||||
|
{ name: "RSA-PKCS1-v1_5" }, clientRSACipher, serverEncryptedRandom);
|
||||||
if (serverRandom === null || serverRandom.length !== 16) {
|
if (serverRandom === null || serverRandom.length !== 16) {
|
||||||
throw new Error("RA2: corrupted server encrypted random");
|
throw new Error("RA2: corrupted server encrypted random");
|
||||||
}
|
}
|
||||||
|
|
@ -494,13 +236,14 @@ export default class RSAAESAuthenticationState extends EventTargetMixin {
|
||||||
clientHash = await window.crypto.subtle.digest("SHA-1", clientHash);
|
clientHash = await window.crypto.subtle.digest("SHA-1", clientHash);
|
||||||
serverHash = new Uint8Array(serverHash);
|
serverHash = new Uint8Array(serverHash);
|
||||||
clientHash = new Uint8Array(clientHash);
|
clientHash = new Uint8Array(clientHash);
|
||||||
this._sock.send(await clientCipher.makeMessage(clientHash));
|
this._sock.sQpushBytes(await clientCipher.makeMessage(clientHash));
|
||||||
|
this._sock.flush();
|
||||||
await this._waitSockAsync(2 + 20 + 16);
|
await this._waitSockAsync(2 + 20 + 16);
|
||||||
if (this._sock.rQshift16() !== 20) {
|
if (this._sock.rQshift16() !== 20) {
|
||||||
throw new Error("RA2: wrong server hash");
|
throw new Error("RA2: wrong server hash");
|
||||||
}
|
}
|
||||||
const serverHashReceived = await serverCipher.receiveMessage(
|
const serverHashReceived = await serverCipher.receiveMessage(
|
||||||
20, this._sock.rQshiftBytes(20), this._sock.rQshiftBytes(16));
|
20, this._sock.rQshiftBytes(20 + 16));
|
||||||
if (serverHashReceived === null) {
|
if (serverHashReceived === null) {
|
||||||
throw new Error("RA2: failed to authenticate the message");
|
throw new Error("RA2: failed to authenticate the message");
|
||||||
}
|
}
|
||||||
|
|
@ -516,11 +259,12 @@ export default class RSAAESAuthenticationState extends EventTargetMixin {
|
||||||
throw new Error("RA2: wrong subtype");
|
throw new Error("RA2: wrong subtype");
|
||||||
}
|
}
|
||||||
let subtype = (await serverCipher.receiveMessage(
|
let subtype = (await serverCipher.receiveMessage(
|
||||||
1, this._sock.rQshiftBytes(1), this._sock.rQshiftBytes(16)));
|
1, this._sock.rQshiftBytes(1 + 16)));
|
||||||
if (subtype === null) {
|
if (subtype === null) {
|
||||||
throw new Error("RA2: failed to authenticate the message");
|
throw new Error("RA2: failed to authenticate the message");
|
||||||
}
|
}
|
||||||
subtype = subtype[0];
|
subtype = subtype[0];
|
||||||
|
let waitCredentials = this._waitCredentialsAsync(subtype);
|
||||||
if (subtype === 1) {
|
if (subtype === 1) {
|
||||||
if (this._getCredentials().username === undefined ||
|
if (this._getCredentials().username === undefined ||
|
||||||
this._getCredentials().password === undefined) {
|
this._getCredentials().password === undefined) {
|
||||||
|
|
@ -537,7 +281,7 @@ export default class RSAAESAuthenticationState extends EventTargetMixin {
|
||||||
} else {
|
} else {
|
||||||
throw new Error("RA2: wrong subtype");
|
throw new Error("RA2: wrong subtype");
|
||||||
}
|
}
|
||||||
await this._waitCredentialsAsync(subtype);
|
await waitCredentials;
|
||||||
let username;
|
let username;
|
||||||
if (subtype === 1) {
|
if (subtype === 1) {
|
||||||
username = encodeUTF8(this._getCredentials().username).slice(0, 255);
|
username = encodeUTF8(this._getCredentials().username).slice(0, 255);
|
||||||
|
|
@ -554,7 +298,8 @@ export default class RSAAESAuthenticationState extends EventTargetMixin {
|
||||||
for (let i = 0; i < password.length; i++) {
|
for (let i = 0; i < password.length; i++) {
|
||||||
credentials[username.length + 2 + i] = password.charCodeAt(i);
|
credentials[username.length + 2 + i] = password.charCodeAt(i);
|
||||||
}
|
}
|
||||||
this._sock.send(await clientCipher.makeMessage(credentials));
|
this._sock.sQpushBytes(await clientCipher.makeMessage(credentials));
|
||||||
|
this._sock.flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
get hasStarted() {
|
get hasStarted() {
|
||||||
|
|
|
||||||
550
core/rfb.js
550
core/rfb.js
|
|
@ -21,12 +21,11 @@ import Keyboard from "./input/keyboard.js";
|
||||||
import GestureHandler from "./input/gesturehandler.js";
|
import GestureHandler from "./input/gesturehandler.js";
|
||||||
import Cursor from "./util/cursor.js";
|
import Cursor from "./util/cursor.js";
|
||||||
import Websock from "./websock.js";
|
import Websock from "./websock.js";
|
||||||
import DES from "./des.js";
|
|
||||||
import KeyTable from "./input/keysym.js";
|
import KeyTable from "./input/keysym.js";
|
||||||
import XtScancode from "./input/xtscancodes.js";
|
import XtScancode from "./input/xtscancodes.js";
|
||||||
import { encodings } from "./encodings.js";
|
import { encodings } from "./encodings.js";
|
||||||
import RSAAESAuthenticationState from "./ra2.js";
|
import RSAAESAuthenticationState from "./ra2.js";
|
||||||
import { MD5 } from "./util/md5.js";
|
import legacyCrypto from "./crypto/crypto.js";
|
||||||
|
|
||||||
import RawDecoder from "./decoders/raw.js";
|
import RawDecoder from "./decoders/raw.js";
|
||||||
import CopyRectDecoder from "./decoders/copyrect.js";
|
import CopyRectDecoder from "./decoders/copyrect.js";
|
||||||
|
|
@ -258,7 +257,6 @@ export default class RFB extends EventTargetMixin {
|
||||||
Log.Error("Display exception: " + exc);
|
Log.Error("Display exception: " + exc);
|
||||||
throw exc;
|
throw exc;
|
||||||
}
|
}
|
||||||
this._display.onflush = this._onFlush.bind(this);
|
|
||||||
|
|
||||||
this._keyboard = new Keyboard(this._canvas);
|
this._keyboard = new Keyboard(this._canvas);
|
||||||
this._keyboard.onkeyevent = this._handleKeyEvent.bind(this);
|
this._keyboard.onkeyevent = this._handleKeyEvent.bind(this);
|
||||||
|
|
@ -960,7 +958,7 @@ export default class RFB extends EventTargetMixin {
|
||||||
}
|
}
|
||||||
|
|
||||||
_handleMessage() {
|
_handleMessage() {
|
||||||
if (this._sock.rQlen === 0) {
|
if (this._sock.rQwait("message", 1)) {
|
||||||
Log.Warn("handleMessage called on an empty receive queue");
|
Log.Warn("handleMessage called on an empty receive queue");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -977,7 +975,7 @@ export default class RFB extends EventTargetMixin {
|
||||||
if (!this._normalMsg()) {
|
if (!this._normalMsg()) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (this._sock.rQlen === 0) {
|
if (this._sock.rQwait("message", 1)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1383,7 +1381,8 @@ export default class RFB extends EventTargetMixin {
|
||||||
while (repeaterID.length < 250) {
|
while (repeaterID.length < 250) {
|
||||||
repeaterID += "\0";
|
repeaterID += "\0";
|
||||||
}
|
}
|
||||||
this._sock.sendString(repeaterID);
|
this._sock.sQpushString(repeaterID);
|
||||||
|
this._sock.flush();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1393,7 +1392,8 @@ export default class RFB extends EventTargetMixin {
|
||||||
|
|
||||||
const cversion = "00" + parseInt(this._rfbVersion, 10) +
|
const cversion = "00" + parseInt(this._rfbVersion, 10) +
|
||||||
".00" + ((this._rfbVersion * 10) % 10);
|
".00" + ((this._rfbVersion * 10) % 10);
|
||||||
this._sock.sendString("RFB " + cversion + "\n");
|
this._sock.sQpushString("RFB " + cversion + "\n");
|
||||||
|
this._sock.flush();
|
||||||
Log.Debug('Sent ProtocolVersion: ' + cversion);
|
Log.Debug('Sent ProtocolVersion: ' + cversion);
|
||||||
|
|
||||||
this._rfbInitState = 'Security';
|
this._rfbInitState = 'Security';
|
||||||
|
|
@ -1445,7 +1445,8 @@ export default class RFB extends EventTargetMixin {
|
||||||
return this._fail("Unsupported security types (types: " + types + ")");
|
return this._fail("Unsupported security types (types: " + types + ")");
|
||||||
}
|
}
|
||||||
|
|
||||||
this._sock.send([this._rfbAuthScheme]);
|
this._sock.sQpush8(this._rfbAuthScheme);
|
||||||
|
this._sock.flush();
|
||||||
} else {
|
} else {
|
||||||
// Server decides
|
// Server decides
|
||||||
if (this._sock.rQwait("security scheme", 4)) { return false; }
|
if (this._sock.rQwait("security scheme", 4)) { return false; }
|
||||||
|
|
@ -1507,12 +1508,15 @@ export default class RFB extends EventTargetMixin {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const xvpAuthStr = String.fromCharCode(this._rfbCredentials.username.length) +
|
this._sock.sQpush8(this._rfbCredentials.username.length);
|
||||||
String.fromCharCode(this._rfbCredentials.target.length) +
|
this._sock.sQpush8(this._rfbCredentials.target.length);
|
||||||
this._rfbCredentials.username +
|
this._sock.sQpushString(this._rfbCredentials.username);
|
||||||
this._rfbCredentials.target;
|
this._sock.sQpushString(this._rfbCredentials.target);
|
||||||
this._sock.sendString(xvpAuthStr);
|
|
||||||
|
this._sock.flush();
|
||||||
|
|
||||||
this._rfbAuthScheme = securityTypeVNCAuth;
|
this._rfbAuthScheme = securityTypeVNCAuth;
|
||||||
|
|
||||||
return this._negotiateAuthentication();
|
return this._negotiateAuthentication();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1530,7 +1534,9 @@ export default class RFB extends EventTargetMixin {
|
||||||
return this._fail("Unsupported VeNCrypt version " + major + "." + minor);
|
return this._fail("Unsupported VeNCrypt version " + major + "." + minor);
|
||||||
}
|
}
|
||||||
|
|
||||||
this._sock.send([0, 2]);
|
this._sock.sQpush8(0);
|
||||||
|
this._sock.sQpush8(2);
|
||||||
|
this._sock.flush();
|
||||||
this._rfbVeNCryptState = 1;
|
this._rfbVeNCryptState = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1589,12 +1595,10 @@ export default class RFB extends EventTargetMixin {
|
||||||
return this._fail("Unsupported security types (types: " + subtypes + ")");
|
return this._fail("Unsupported security types (types: " + subtypes + ")");
|
||||||
}
|
}
|
||||||
|
|
||||||
this._sock.send([this._rfbAuthScheme >> 24,
|
this._sock.sQpush32(this._rfbAuthScheme);
|
||||||
this._rfbAuthScheme >> 16,
|
this._sock.flush();
|
||||||
this._rfbAuthScheme >> 8,
|
|
||||||
this._rfbAuthScheme]);
|
|
||||||
|
|
||||||
this._rfbVeNCryptState == 4;
|
this._rfbVeNCryptState = 4;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1611,20 +1615,11 @@ export default class RFB extends EventTargetMixin {
|
||||||
const user = encodeUTF8(this._rfbCredentials.username);
|
const user = encodeUTF8(this._rfbCredentials.username);
|
||||||
const pass = encodeUTF8(this._rfbCredentials.password);
|
const pass = encodeUTF8(this._rfbCredentials.password);
|
||||||
|
|
||||||
this._sock.send([
|
this._sock.sQpush32(user.length);
|
||||||
(user.length >> 24) & 0xFF,
|
this._sock.sQpush32(pass.length);
|
||||||
(user.length >> 16) & 0xFF,
|
this._sock.sQpushString(user);
|
||||||
(user.length >> 8) & 0xFF,
|
this._sock.sQpushString(pass);
|
||||||
user.length & 0xFF
|
this._sock.flush();
|
||||||
]);
|
|
||||||
this._sock.send([
|
|
||||||
(pass.length >> 24) & 0xFF,
|
|
||||||
(pass.length >> 16) & 0xFF,
|
|
||||||
(pass.length >> 8) & 0xFF,
|
|
||||||
pass.length & 0xFF
|
|
||||||
]);
|
|
||||||
this._sock.sendString(user);
|
|
||||||
this._sock.sendString(pass);
|
|
||||||
|
|
||||||
this._rfbInitState = "SecurityResult";
|
this._rfbInitState = "SecurityResult";
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -1643,7 +1638,8 @@ export default class RFB extends EventTargetMixin {
|
||||||
// TODO(directxman12): make genDES not require an Array
|
// TODO(directxman12): make genDES not require an Array
|
||||||
const challenge = Array.prototype.slice.call(this._sock.rQshiftBytes(16));
|
const challenge = Array.prototype.slice.call(this._sock.rQshiftBytes(16));
|
||||||
const response = RFB.genDES(this._rfbCredentials.password, challenge);
|
const response = RFB.genDES(this._rfbCredentials.password, challenge);
|
||||||
this._sock.send(response);
|
this._sock.sQpushBytes(response);
|
||||||
|
this._sock.flush();
|
||||||
this._rfbInitState = "SecurityResult";
|
this._rfbInitState = "SecurityResult";
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -1661,8 +1657,9 @@ export default class RFB extends EventTargetMixin {
|
||||||
if (this._rfbCredentials.ardPublicKey != undefined &&
|
if (this._rfbCredentials.ardPublicKey != undefined &&
|
||||||
this._rfbCredentials.ardCredentials != undefined) {
|
this._rfbCredentials.ardCredentials != undefined) {
|
||||||
// if the async web crypto is done return the results
|
// if the async web crypto is done return the results
|
||||||
this._sock.send(this._rfbCredentials.ardCredentials);
|
this._sock.sQpushBytes(this._rfbCredentials.ardCredentials);
|
||||||
this._sock.send(this._rfbCredentials.ardPublicKey);
|
this._sock.sQpushBytes(this._rfbCredentials.ardPublicKey);
|
||||||
|
this._sock.flush();
|
||||||
this._rfbCredentials.ardCredentials = null;
|
this._rfbCredentials.ardCredentials = null;
|
||||||
this._rfbCredentials.ardPublicKey = null;
|
this._rfbCredentials.ardPublicKey = null;
|
||||||
this._rfbInitState = "SecurityResult";
|
this._rfbInitState = "SecurityResult";
|
||||||
|
|
@ -1681,77 +1678,35 @@ export default class RFB extends EventTargetMixin {
|
||||||
let prime = this._sock.rQshiftBytes(keyLength); // predetermined prime modulus
|
let prime = this._sock.rQshiftBytes(keyLength); // predetermined prime modulus
|
||||||
let serverPublicKey = this._sock.rQshiftBytes(keyLength); // other party's public key
|
let serverPublicKey = this._sock.rQshiftBytes(keyLength); // other party's public key
|
||||||
|
|
||||||
let clientPrivateKey = window.crypto.getRandomValues(new Uint8Array(keyLength));
|
let clientKey = legacyCrypto.generateKey(
|
||||||
let padding = Array.from(window.crypto.getRandomValues(new Uint8Array(64)), byte => String.fromCharCode(65+byte%26)).join('');
|
{ name: "DH", g: generator, p: prime }, false, ["deriveBits"]);
|
||||||
|
this._negotiateARDAuthAsync(keyLength, serverPublicKey, clientKey);
|
||||||
this._negotiateARDAuthAsync(generator, keyLength, prime, serverPublicKey, clientPrivateKey, padding);
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
_modPow(base, exponent, modulus) {
|
async _negotiateARDAuthAsync(keyLength, serverPublicKey, clientKey) {
|
||||||
|
const clientPublicKey = legacyCrypto.exportKey("raw", clientKey.publicKey);
|
||||||
|
const sharedKey = legacyCrypto.deriveBits(
|
||||||
|
{ name: "DH", public: serverPublicKey }, clientKey.privateKey, keyLength * 8);
|
||||||
|
|
||||||
let baseHex = "0x"+Array.from(base, byte => ('0' + (byte & 0xFF).toString(16)).slice(-2)).join('');
|
const username = encodeUTF8(this._rfbCredentials.username).substring(0, 63);
|
||||||
let exponentHex = "0x"+Array.from(exponent, byte => ('0' + (byte & 0xFF).toString(16)).slice(-2)).join('');
|
const password = encodeUTF8(this._rfbCredentials.password).substring(0, 63);
|
||||||
let modulusHex = "0x"+Array.from(modulus, byte => ('0' + (byte & 0xFF).toString(16)).slice(-2)).join('');
|
|
||||||
|
|
||||||
let b = BigInt(baseHex);
|
const credentials = window.crypto.getRandomValues(new Uint8Array(128));
|
||||||
let e = BigInt(exponentHex);
|
for (let i = 0; i < username.length; i++) {
|
||||||
let m = BigInt(modulusHex);
|
credentials[i] = username.charCodeAt(i);
|
||||||
let r = 1n;
|
|
||||||
b = b % m;
|
|
||||||
while (e > 0) {
|
|
||||||
if (e % 2n === 1n) {
|
|
||||||
r = (r * b) % m;
|
|
||||||
}
|
|
||||||
e = e / 2n;
|
|
||||||
b = (b * b) % m;
|
|
||||||
}
|
}
|
||||||
let hexResult = r.toString(16);
|
credentials[username.length] = 0;
|
||||||
|
for (let i = 0; i < password.length; i++) {
|
||||||
while (hexResult.length/2<exponent.length || (hexResult.length%2 != 0)) {
|
credentials[64 + i] = password.charCodeAt(i);
|
||||||
hexResult = "0"+hexResult;
|
|
||||||
}
|
}
|
||||||
|
credentials[64 + password.length] = 0;
|
||||||
|
|
||||||
let bytesResult = [];
|
const key = await legacyCrypto.digest("MD5", sharedKey);
|
||||||
for (let c = 0; c < hexResult.length; c += 2) {
|
const cipher = await legacyCrypto.importKey(
|
||||||
bytesResult.push(parseInt(hexResult.substr(c, 2), 16));
|
"raw", key, { name: "AES-ECB" }, false, ["encrypt"]);
|
||||||
}
|
const encrypted = await legacyCrypto.encrypt({ name: "AES-ECB" }, cipher, credentials);
|
||||||
return bytesResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
async _aesEcbEncrypt(string, key) {
|
|
||||||
// perform AES-ECB blocks
|
|
||||||
let keyString = Array.from(key, byte => String.fromCharCode(byte)).join('');
|
|
||||||
let aesKey = await window.crypto.subtle.importKey("raw", MD5(keyString), {name: "AES-CBC"}, false, ["encrypt"]);
|
|
||||||
let data = new Uint8Array(string.length);
|
|
||||||
for (let i = 0; i < string.length; ++i) {
|
|
||||||
data[i] = string.charCodeAt(i);
|
|
||||||
}
|
|
||||||
let encrypted = new Uint8Array(data.length);
|
|
||||||
for (let i=0;i<data.length;i+=16) {
|
|
||||||
let block = data.slice(i, i+16);
|
|
||||||
let encryptedBlock = await window.crypto.subtle.encrypt({name: "AES-CBC", iv: block},
|
|
||||||
aesKey, new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
|
|
||||||
);
|
|
||||||
encrypted.set((new Uint8Array(encryptedBlock)).slice(0, 16), i);
|
|
||||||
}
|
|
||||||
return encrypted;
|
|
||||||
}
|
|
||||||
|
|
||||||
async _negotiateARDAuthAsync(generator, keyLength, prime, serverPublicKey, clientPrivateKey, padding) {
|
|
||||||
// calculate the DH keys
|
|
||||||
let clientPublicKey = this._modPow(generator, clientPrivateKey, prime);
|
|
||||||
let sharedKey = this._modPow(serverPublicKey, clientPrivateKey, prime);
|
|
||||||
|
|
||||||
let username = encodeUTF8(this._rfbCredentials.username).substring(0, 63);
|
|
||||||
let password = encodeUTF8(this._rfbCredentials.password).substring(0, 63);
|
|
||||||
|
|
||||||
let paddedUsername = username + '\0' + padding.substring(0, 63);
|
|
||||||
let paddedPassword = password + '\0' + padding.substring(0, 63);
|
|
||||||
let credentials = paddedUsername.substring(0, 64) + paddedPassword.substring(0, 64);
|
|
||||||
|
|
||||||
let encrypted = await this._aesEcbEncrypt(credentials, sharedKey);
|
|
||||||
|
|
||||||
this._rfbCredentials.ardCredentials = encrypted;
|
this._rfbCredentials.ardCredentials = encrypted;
|
||||||
this._rfbCredentials.ardPublicKey = clientPublicKey;
|
this._rfbCredentials.ardPublicKey = clientPublicKey;
|
||||||
|
|
@ -1768,10 +1723,12 @@ export default class RFB extends EventTargetMixin {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
this._sock.send([0, 0, 0, this._rfbCredentials.username.length]);
|
this._sock.sQpush32(this._rfbCredentials.username.length);
|
||||||
this._sock.send([0, 0, 0, this._rfbCredentials.password.length]);
|
this._sock.sQpush32(this._rfbCredentials.password.length);
|
||||||
this._sock.sendString(this._rfbCredentials.username);
|
this._sock.sQpushString(this._rfbCredentials.username);
|
||||||
this._sock.sendString(this._rfbCredentials.password);
|
this._sock.sQpushString(this._rfbCredentials.password);
|
||||||
|
this._sock.flush();
|
||||||
|
|
||||||
this._rfbInitState = "SecurityResult";
|
this._rfbInitState = "SecurityResult";
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -1809,7 +1766,8 @@ export default class RFB extends EventTargetMixin {
|
||||||
"vendor or signature");
|
"vendor or signature");
|
||||||
}
|
}
|
||||||
Log.Debug("Selected tunnel type: " + clientSupportedTunnelTypes[0]);
|
Log.Debug("Selected tunnel type: " + clientSupportedTunnelTypes[0]);
|
||||||
this._sock.send([0, 0, 0, 0]); // use NOTUNNEL
|
this._sock.sQpush32(0); // use NOTUNNEL
|
||||||
|
this._sock.flush();
|
||||||
return false; // wait until we receive the sub auth count to continue
|
return false; // wait until we receive the sub auth count to continue
|
||||||
} else {
|
} else {
|
||||||
return this._fail("Server wanted tunnels, but doesn't support " +
|
return this._fail("Server wanted tunnels, but doesn't support " +
|
||||||
|
|
@ -1859,7 +1817,8 @@ export default class RFB extends EventTargetMixin {
|
||||||
|
|
||||||
for (let authType in clientSupportedTypes) {
|
for (let authType in clientSupportedTypes) {
|
||||||
if (serverSupportedTypes.indexOf(authType) != -1) {
|
if (serverSupportedTypes.indexOf(authType) != -1) {
|
||||||
this._sock.send([0, 0, 0, clientSupportedTypes[authType]]);
|
this._sock.sQpush32(clientSupportedTypes[authType]);
|
||||||
|
this._sock.flush();
|
||||||
Log.Debug("Selected authentication type: " + authType);
|
Log.Debug("Selected authentication type: " + authType);
|
||||||
|
|
||||||
switch (authType) {
|
switch (authType) {
|
||||||
|
|
@ -1905,8 +1864,8 @@ export default class RFB extends EventTargetMixin {
|
||||||
if (e.message !== "disconnect normally") {
|
if (e.message !== "disconnect normally") {
|
||||||
this._fail(e.message);
|
this._fail(e.message);
|
||||||
}
|
}
|
||||||
}).then(() => {
|
})
|
||||||
this.dispatchEvent(new CustomEvent('securityresult'));
|
.then(() => {
|
||||||
this._rfbInitState = "SecurityResult";
|
this._rfbInitState = "SecurityResult";
|
||||||
return true;
|
return true;
|
||||||
}).finally(() => {
|
}).finally(() => {
|
||||||
|
|
@ -1934,15 +1893,15 @@ export default class RFB extends EventTargetMixin {
|
||||||
const g = this._sock.rQshiftBytes(8);
|
const g = this._sock.rQshiftBytes(8);
|
||||||
const p = this._sock.rQshiftBytes(8);
|
const p = this._sock.rQshiftBytes(8);
|
||||||
const A = this._sock.rQshiftBytes(8);
|
const A = this._sock.rQshiftBytes(8);
|
||||||
const b = window.crypto.getRandomValues(new Uint8Array(8));
|
const dhKey = legacyCrypto.generateKey({ name: "DH", g: g, p: p }, true, ["deriveBits"]);
|
||||||
const B = new Uint8Array(this._modPow(g, b, p));
|
const B = legacyCrypto.exportKey("raw", dhKey.publicKey);
|
||||||
const secret = new Uint8Array(this._modPow(A, b, p));
|
const secret = legacyCrypto.deriveBits({ name: "DH", public: A }, dhKey.privateKey, 64);
|
||||||
|
|
||||||
const des = new DES(secret);
|
const key = legacyCrypto.importKey("raw", secret, { name: "DES-CBC" }, false, ["encrypt"]);
|
||||||
const username = encodeUTF8(this._rfbCredentials.username).substring(0, 255);
|
const username = encodeUTF8(this._rfbCredentials.username).substring(0, 255);
|
||||||
const password = encodeUTF8(this._rfbCredentials.password).substring(0, 63);
|
const password = encodeUTF8(this._rfbCredentials.password).substring(0, 63);
|
||||||
const usernameBytes = new Uint8Array(256);
|
let usernameBytes = new Uint8Array(256);
|
||||||
const passwordBytes = new Uint8Array(64);
|
let passwordBytes = new Uint8Array(64);
|
||||||
window.crypto.getRandomValues(usernameBytes);
|
window.crypto.getRandomValues(usernameBytes);
|
||||||
window.crypto.getRandomValues(passwordBytes);
|
window.crypto.getRandomValues(passwordBytes);
|
||||||
for (let i = 0; i < username.length; i++) {
|
for (let i = 0; i < username.length; i++) {
|
||||||
|
|
@ -1953,25 +1912,12 @@ export default class RFB extends EventTargetMixin {
|
||||||
passwordBytes[i] = password.charCodeAt(i);
|
passwordBytes[i] = password.charCodeAt(i);
|
||||||
}
|
}
|
||||||
passwordBytes[password.length] = 0;
|
passwordBytes[password.length] = 0;
|
||||||
let x = new Uint8Array(secret);
|
usernameBytes = legacyCrypto.encrypt({ name: "DES-CBC", iv: secret }, key, usernameBytes);
|
||||||
for (let i = 0; i < 32; i++) {
|
passwordBytes = legacyCrypto.encrypt({ name: "DES-CBC", iv: secret }, key, passwordBytes);
|
||||||
for (let j = 0; j < 8; j++) {
|
this._sock.sQpushBytes(B);
|
||||||
x[j] ^= usernameBytes[i * 8 + j];
|
this._sock.sQpushBytes(usernameBytes);
|
||||||
}
|
this._sock.sQpushBytes(passwordBytes);
|
||||||
x = des.enc8(x);
|
this._sock.flush();
|
||||||
usernameBytes.set(x, i * 8);
|
|
||||||
}
|
|
||||||
x = new Uint8Array(secret);
|
|
||||||
for (let i = 0; i < 8; i++) {
|
|
||||||
for (let j = 0; j < 8; j++) {
|
|
||||||
x[j] ^= passwordBytes[i * 8 + j];
|
|
||||||
}
|
|
||||||
x = des.enc8(x);
|
|
||||||
passwordBytes.set(x, i * 8);
|
|
||||||
}
|
|
||||||
this._sock.send(B);
|
|
||||||
this._sock.send(usernameBytes);
|
|
||||||
this._sock.send(passwordBytes);
|
|
||||||
this._rfbInitState = "SecurityResult";
|
this._rfbInitState = "SecurityResult";
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -2199,7 +2145,8 @@ export default class RFB extends EventTargetMixin {
|
||||||
return this._handleSecurityReason();
|
return this._handleSecurityReason();
|
||||||
|
|
||||||
case 'ClientInitialisation':
|
case 'ClientInitialisation':
|
||||||
this._sock.send([this._shared ? 1 : 0]); // ClientInitialisation
|
this._sock.sQpush8(this._shared ? 1 : 0); // ClientInitialisation
|
||||||
|
this._sock.flush();
|
||||||
this._rfbInitState = 'ServerInitialisation';
|
this._rfbInitState = 'ServerInitialisation';
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
|
@ -2512,19 +2459,11 @@ export default class RFB extends EventTargetMixin {
|
||||||
|
|
||||||
default:
|
default:
|
||||||
this._fail("Unexpected server message (type " + msgType + ")");
|
this._fail("Unexpected server message (type " + msgType + ")");
|
||||||
Log.Debug("sock.rQslice(0, 30): " + this._sock.rQslice(0, 30));
|
Log.Debug("sock.rQpeekBytes(30): " + this._sock.rQpeekBytes(30));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_onFlush() {
|
|
||||||
this._flushing = false;
|
|
||||||
// Resume processing
|
|
||||||
if (this._sock.rQlen > 0) {
|
|
||||||
this._handleMessage();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_framebufferUpdate() {
|
_framebufferUpdate() {
|
||||||
if (this._FBU.rects === 0) {
|
if (this._FBU.rects === 0) {
|
||||||
if (this._sock.rQwait("FBU header", 3, 1)) { return false; }
|
if (this._sock.rQwait("FBU header", 3, 1)) { return false; }
|
||||||
|
|
@ -2535,7 +2474,14 @@ export default class RFB extends EventTargetMixin {
|
||||||
// to avoid building up an excessive queue
|
// to avoid building up an excessive queue
|
||||||
if (this._display.pending()) {
|
if (this._display.pending()) {
|
||||||
this._flushing = true;
|
this._flushing = true;
|
||||||
this._display.flush();
|
this._display.flush()
|
||||||
|
.then(() => {
|
||||||
|
this._flushing = false;
|
||||||
|
// Resume processing
|
||||||
|
if (!this._sock.rQwait("message", 1)) {
|
||||||
|
this._handleMessage();
|
||||||
|
}
|
||||||
|
});
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2545,13 +2491,13 @@ export default class RFB extends EventTargetMixin {
|
||||||
if (this._sock.rQwait("rect header", 12)) { return false; }
|
if (this._sock.rQwait("rect header", 12)) { return false; }
|
||||||
/* New FramebufferUpdate */
|
/* New FramebufferUpdate */
|
||||||
|
|
||||||
const hdr = this._sock.rQshiftBytes(12);
|
this._FBU.x = this._sock.rQshift16();
|
||||||
this._FBU.x = (hdr[0] << 8) + hdr[1];
|
this._FBU.y = this._sock.rQshift16();
|
||||||
this._FBU.y = (hdr[2] << 8) + hdr[3];
|
this._FBU.width = this._sock.rQshift16();
|
||||||
this._FBU.width = (hdr[4] << 8) + hdr[5];
|
this._FBU.height = this._sock.rQshift16();
|
||||||
this._FBU.height = (hdr[6] << 8) + hdr[7];
|
this._FBU.encoding = this._sock.rQshift32();
|
||||||
this._FBU.encoding = parseInt((hdr[8] << 24) + (hdr[9] << 16) +
|
/* Encodings are signed */
|
||||||
(hdr[10] << 8) + hdr[11], 10);
|
this._FBU.encoding >>= 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this._handleRect()) {
|
if (!this._handleRect()) {
|
||||||
|
|
@ -2785,26 +2731,18 @@ export default class RFB extends EventTargetMixin {
|
||||||
const firstUpdate = !this._supportsSetDesktopSize;
|
const firstUpdate = !this._supportsSetDesktopSize;
|
||||||
this._supportsSetDesktopSize = true;
|
this._supportsSetDesktopSize = true;
|
||||||
|
|
||||||
// Normally we only apply the current resize mode after a
|
|
||||||
// window resize event. However there is no such trigger on the
|
|
||||||
// initial connect. And we don't know if the server supports
|
|
||||||
// resizing until we've gotten here.
|
|
||||||
if (firstUpdate) {
|
|
||||||
this._requestRemoteResize();
|
|
||||||
}
|
|
||||||
|
|
||||||
this._sock.rQskipBytes(1); // number-of-screens
|
this._sock.rQskipBytes(1); // number-of-screens
|
||||||
this._sock.rQskipBytes(3); // padding
|
this._sock.rQskipBytes(3); // padding
|
||||||
|
|
||||||
for (let i = 0; i < numberOfScreens; i += 1) {
|
for (let i = 0; i < numberOfScreens; i += 1) {
|
||||||
// Save the id and flags of the first screen
|
// Save the id and flags of the first screen
|
||||||
if (i === 0) {
|
if (i === 0) {
|
||||||
this._screenID = this._sock.rQshiftBytes(4); // id
|
this._screenID = this._sock.rQshift32(); // id
|
||||||
this._sock.rQskipBytes(2); // x-position
|
this._sock.rQskipBytes(2); // x-position
|
||||||
this._sock.rQskipBytes(2); // y-position
|
this._sock.rQskipBytes(2); // y-position
|
||||||
this._sock.rQskipBytes(2); // width
|
this._sock.rQskipBytes(2); // width
|
||||||
this._sock.rQskipBytes(2); // height
|
this._sock.rQskipBytes(2); // height
|
||||||
this._screenFlags = this._sock.rQshiftBytes(4); // flags
|
this._screenFlags = this._sock.rQshift32(); // flags
|
||||||
} else {
|
} else {
|
||||||
this._sock.rQskipBytes(16);
|
this._sock.rQskipBytes(16);
|
||||||
}
|
}
|
||||||
|
|
@ -2842,6 +2780,14 @@ export default class RFB extends EventTargetMixin {
|
||||||
this._resize(this._FBU.width, this._FBU.height);
|
this._resize(this._FBU.width, this._FBU.height);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Normally we only apply the current resize mode after a
|
||||||
|
// window resize event. However there is no such trigger on the
|
||||||
|
// initial connect. And we don't know if the server supports
|
||||||
|
// resizing until we've gotten here.
|
||||||
|
if (firstUpdate) {
|
||||||
|
this._requestRemoteResize();
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2937,28 +2883,22 @@ export default class RFB extends EventTargetMixin {
|
||||||
|
|
||||||
static genDES(password, challenge) {
|
static genDES(password, challenge) {
|
||||||
const passwordChars = password.split('').map(c => c.charCodeAt(0));
|
const passwordChars = password.split('').map(c => c.charCodeAt(0));
|
||||||
return (new DES(passwordChars)).encrypt(challenge);
|
const key = legacyCrypto.importKey(
|
||||||
|
"raw", passwordChars, { name: "DES-ECB" }, false, ["encrypt"]);
|
||||||
|
return legacyCrypto.encrypt({ name: "DES-ECB" }, key, challenge);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Class Methods
|
// Class Methods
|
||||||
RFB.messages = {
|
RFB.messages = {
|
||||||
keyEvent(sock, keysym, down) {
|
keyEvent(sock, keysym, down) {
|
||||||
const buff = sock._sQ;
|
sock.sQpush8(4); // msg-type
|
||||||
const offset = sock._sQlen;
|
sock.sQpush8(down);
|
||||||
|
|
||||||
buff[offset] = 4; // msg-type
|
sock.sQpush16(0);
|
||||||
buff[offset + 1] = down;
|
|
||||||
|
|
||||||
buff[offset + 2] = 0;
|
sock.sQpush32(keysym);
|
||||||
buff[offset + 3] = 0;
|
|
||||||
|
|
||||||
buff[offset + 4] = (keysym >> 24);
|
|
||||||
buff[offset + 5] = (keysym >> 16);
|
|
||||||
buff[offset + 6] = (keysym >> 8);
|
|
||||||
buff[offset + 7] = keysym;
|
|
||||||
|
|
||||||
sock._sQlen += 8;
|
|
||||||
sock.flush();
|
sock.flush();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -2972,46 +2912,28 @@ RFB.messages = {
|
||||||
return xtScanCode;
|
return xtScanCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
const buff = sock._sQ;
|
sock.sQpush8(255); // msg-type
|
||||||
const offset = sock._sQlen;
|
sock.sQpush8(0); // sub msg-type
|
||||||
|
|
||||||
buff[offset] = 255; // msg-type
|
sock.sQpush16(down);
|
||||||
buff[offset + 1] = 0; // sub msg-type
|
|
||||||
|
|
||||||
buff[offset + 2] = (down >> 8);
|
sock.sQpush32(keysym);
|
||||||
buff[offset + 3] = down;
|
|
||||||
|
|
||||||
buff[offset + 4] = (keysym >> 24);
|
|
||||||
buff[offset + 5] = (keysym >> 16);
|
|
||||||
buff[offset + 6] = (keysym >> 8);
|
|
||||||
buff[offset + 7] = keysym;
|
|
||||||
|
|
||||||
const RFBkeycode = getRFBkeycode(keycode);
|
const RFBkeycode = getRFBkeycode(keycode);
|
||||||
|
|
||||||
buff[offset + 8] = (RFBkeycode >> 24);
|
sock.sQpush32(RFBkeycode);
|
||||||
buff[offset + 9] = (RFBkeycode >> 16);
|
|
||||||
buff[offset + 10] = (RFBkeycode >> 8);
|
|
||||||
buff[offset + 11] = RFBkeycode;
|
|
||||||
|
|
||||||
sock._sQlen += 12;
|
|
||||||
sock.flush();
|
sock.flush();
|
||||||
},
|
},
|
||||||
|
|
||||||
pointerEvent(sock, x, y, mask) {
|
pointerEvent(sock, x, y, mask) {
|
||||||
const buff = sock._sQ;
|
sock.sQpush8(5); // msg-type
|
||||||
const offset = sock._sQlen;
|
|
||||||
|
|
||||||
buff[offset] = 5; // msg-type
|
sock.sQpush8(mask);
|
||||||
|
|
||||||
buff[offset + 1] = mask;
|
sock.sQpush16(x);
|
||||||
|
sock.sQpush16(y);
|
||||||
|
|
||||||
buff[offset + 2] = x >> 8;
|
|
||||||
buff[offset + 3] = x;
|
|
||||||
|
|
||||||
buff[offset + 4] = y >> 8;
|
|
||||||
buff[offset + 5] = y;
|
|
||||||
|
|
||||||
sock._sQlen += 6;
|
|
||||||
sock.flush();
|
sock.flush();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -3111,14 +3033,11 @@ RFB.messages = {
|
||||||
},
|
},
|
||||||
|
|
||||||
clientCutText(sock, data, extended = false) {
|
clientCutText(sock, data, extended = false) {
|
||||||
const buff = sock._sQ;
|
sock.sQpush8(6); // msg-type
|
||||||
const offset = sock._sQlen;
|
|
||||||
|
|
||||||
buff[offset] = 6; // msg-type
|
sock.sQpush8(0); // padding
|
||||||
|
sock.sQpush8(0); // padding
|
||||||
buff[offset + 1] = 0; // padding
|
sock.sQpush8(0); // padding
|
||||||
buff[offset + 2] = 0; // padding
|
|
||||||
buff[offset + 3] = 0; // padding
|
|
||||||
|
|
||||||
let length;
|
let length;
|
||||||
if (extended) {
|
if (extended) {
|
||||||
|
|
@ -3127,121 +3046,63 @@ RFB.messages = {
|
||||||
length = data.length;
|
length = data.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
buff[offset + 4] = length >> 24;
|
sock.sQpush32(length);
|
||||||
buff[offset + 5] = length >> 16;
|
sock.sQpushBytes(data);
|
||||||
buff[offset + 6] = length >> 8;
|
sock.flush();
|
||||||
buff[offset + 7] = length;
|
|
||||||
|
|
||||||
sock._sQlen += 8;
|
|
||||||
|
|
||||||
// We have to keep track of from where in the data we begin creating the
|
|
||||||
// buffer for the flush in the next iteration.
|
|
||||||
let dataOffset = 0;
|
|
||||||
|
|
||||||
let remaining = data.length;
|
|
||||||
while (remaining > 0) {
|
|
||||||
|
|
||||||
let flushSize = Math.min(remaining, (sock._sQbufferSize - sock._sQlen));
|
|
||||||
for (let i = 0; i < flushSize; i++) {
|
|
||||||
buff[sock._sQlen + i] = data[dataOffset + i];
|
|
||||||
}
|
|
||||||
|
|
||||||
sock._sQlen += flushSize;
|
|
||||||
sock.flush();
|
|
||||||
|
|
||||||
remaining -= flushSize;
|
|
||||||
dataOffset += flushSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
setDesktopSize(sock, width, height, id, flags) {
|
setDesktopSize(sock, width, height, id, flags) {
|
||||||
const buff = sock._sQ;
|
sock.sQpush8(251); // msg-type
|
||||||
const offset = sock._sQlen;
|
|
||||||
|
|
||||||
buff[offset] = 251; // msg-type
|
sock.sQpush8(0); // padding
|
||||||
buff[offset + 1] = 0; // padding
|
|
||||||
buff[offset + 2] = width >> 8; // width
|
|
||||||
buff[offset + 3] = width;
|
|
||||||
buff[offset + 4] = height >> 8; // height
|
|
||||||
buff[offset + 5] = height;
|
|
||||||
|
|
||||||
buff[offset + 6] = 1; // number-of-screens
|
sock.sQpush16(width);
|
||||||
buff[offset + 7] = 0; // padding
|
sock.sQpush16(height);
|
||||||
|
|
||||||
|
sock.sQpush8(1); // number-of-screens
|
||||||
|
|
||||||
|
sock.sQpush8(0); // padding
|
||||||
|
|
||||||
// screen array
|
// screen array
|
||||||
buff[offset + 8] = id >> 24; // id
|
sock.sQpush32(id);
|
||||||
buff[offset + 9] = id >> 16;
|
sock.sQpush16(0); // x-position
|
||||||
buff[offset + 10] = id >> 8;
|
sock.sQpush16(0); // y-position
|
||||||
buff[offset + 11] = id;
|
sock.sQpush16(width);
|
||||||
buff[offset + 12] = 0; // x-position
|
sock.sQpush16(height);
|
||||||
buff[offset + 13] = 0;
|
sock.sQpush32(flags);
|
||||||
buff[offset + 14] = 0; // y-position
|
|
||||||
buff[offset + 15] = 0;
|
|
||||||
buff[offset + 16] = width >> 8; // width
|
|
||||||
buff[offset + 17] = width;
|
|
||||||
buff[offset + 18] = height >> 8; // height
|
|
||||||
buff[offset + 19] = height;
|
|
||||||
buff[offset + 20] = flags >> 24; // flags
|
|
||||||
buff[offset + 21] = flags >> 16;
|
|
||||||
buff[offset + 22] = flags >> 8;
|
|
||||||
buff[offset + 23] = flags;
|
|
||||||
|
|
||||||
sock._sQlen += 24;
|
|
||||||
sock.flush();
|
sock.flush();
|
||||||
},
|
},
|
||||||
|
|
||||||
clientFence(sock, flags, payload) {
|
clientFence(sock, flags, payload) {
|
||||||
const buff = sock._sQ;
|
sock.sQpush8(248); // msg-type
|
||||||
const offset = sock._sQlen;
|
|
||||||
|
|
||||||
buff[offset] = 248; // msg-type
|
sock.sQpush8(0); // padding
|
||||||
|
sock.sQpush8(0); // padding
|
||||||
|
sock.sQpush8(0); // padding
|
||||||
|
|
||||||
buff[offset + 1] = 0; // padding
|
sock.sQpush32(flags);
|
||||||
buff[offset + 2] = 0; // padding
|
|
||||||
buff[offset + 3] = 0; // padding
|
|
||||||
|
|
||||||
buff[offset + 4] = flags >> 24; // flags
|
sock.sQpush8(payload.length);
|
||||||
buff[offset + 5] = flags >> 16;
|
sock.sQpushString(payload);
|
||||||
buff[offset + 6] = flags >> 8;
|
|
||||||
buff[offset + 7] = flags;
|
|
||||||
|
|
||||||
const n = payload.length;
|
|
||||||
|
|
||||||
buff[offset + 8] = n; // length
|
|
||||||
|
|
||||||
for (let i = 0; i < n; i++) {
|
|
||||||
buff[offset + 9 + i] = payload.charCodeAt(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
sock._sQlen += 9 + n;
|
|
||||||
sock.flush();
|
sock.flush();
|
||||||
},
|
},
|
||||||
|
|
||||||
enableContinuousUpdates(sock, enable, x, y, width, height) {
|
enableContinuousUpdates(sock, enable, x, y, width, height) {
|
||||||
const buff = sock._sQ;
|
sock.sQpush8(150); // msg-type
|
||||||
const offset = sock._sQlen;
|
|
||||||
|
|
||||||
buff[offset] = 150; // msg-type
|
sock.sQpush8(enable);
|
||||||
buff[offset + 1] = enable; // enable-flag
|
|
||||||
|
|
||||||
buff[offset + 2] = x >> 8; // x
|
sock.sQpush16(x);
|
||||||
buff[offset + 3] = x;
|
sock.sQpush16(y);
|
||||||
buff[offset + 4] = y >> 8; // y
|
sock.sQpush16(width);
|
||||||
buff[offset + 5] = y;
|
sock.sQpush16(height);
|
||||||
buff[offset + 6] = width >> 8; // width
|
|
||||||
buff[offset + 7] = width;
|
|
||||||
buff[offset + 8] = height >> 8; // height
|
|
||||||
buff[offset + 9] = height;
|
|
||||||
|
|
||||||
sock._sQlen += 10;
|
|
||||||
sock.flush();
|
sock.flush();
|
||||||
},
|
},
|
||||||
|
|
||||||
pixelFormat(sock, depth, trueColor) {
|
pixelFormat(sock, depth, trueColor) {
|
||||||
const buff = sock._sQ;
|
|
||||||
const offset = sock._sQlen;
|
|
||||||
|
|
||||||
let bpp;
|
let bpp;
|
||||||
|
|
||||||
if (depth > 16) {
|
if (depth > 16) {
|
||||||
|
|
@ -3254,100 +3115,69 @@ RFB.messages = {
|
||||||
|
|
||||||
const bits = Math.floor(depth/3);
|
const bits = Math.floor(depth/3);
|
||||||
|
|
||||||
buff[offset] = 0; // msg-type
|
sock.sQpush8(0); // msg-type
|
||||||
|
|
||||||
buff[offset + 1] = 0; // padding
|
sock.sQpush8(0); // padding
|
||||||
buff[offset + 2] = 0; // padding
|
sock.sQpush8(0); // padding
|
||||||
buff[offset + 3] = 0; // padding
|
sock.sQpush8(0); // padding
|
||||||
|
|
||||||
buff[offset + 4] = bpp; // bits-per-pixel
|
sock.sQpush8(bpp);
|
||||||
buff[offset + 5] = depth; // depth
|
sock.sQpush8(depth);
|
||||||
buff[offset + 6] = 0; // little-endian
|
sock.sQpush8(0); // little-endian
|
||||||
buff[offset + 7] = trueColor ? 1 : 0; // true-color
|
sock.sQpush8(trueColor ? 1 : 0);
|
||||||
|
|
||||||
buff[offset + 8] = 0; // red-max
|
sock.sQpush16((1 << bits) - 1); // red-max
|
||||||
buff[offset + 9] = (1 << bits) - 1; // red-max
|
sock.sQpush16((1 << bits) - 1); // green-max
|
||||||
|
sock.sQpush16((1 << bits) - 1); // blue-max
|
||||||
|
|
||||||
buff[offset + 10] = 0; // green-max
|
sock.sQpush8(bits * 0); // red-shift
|
||||||
buff[offset + 11] = (1 << bits) - 1; // green-max
|
sock.sQpush8(bits * 1); // green-shift
|
||||||
|
sock.sQpush8(bits * 2); // blue-shift
|
||||||
|
|
||||||
buff[offset + 12] = 0; // blue-max
|
sock.sQpush8(0); // padding
|
||||||
buff[offset + 13] = (1 << bits) - 1; // blue-max
|
sock.sQpush8(0); // padding
|
||||||
|
sock.sQpush8(0); // padding
|
||||||
|
|
||||||
buff[offset + 14] = bits * 0; // red-shift
|
|
||||||
buff[offset + 15] = bits * 1; // green-shift
|
|
||||||
buff[offset + 16] = bits * 2; // blue-shift
|
|
||||||
|
|
||||||
buff[offset + 17] = 0; // padding
|
|
||||||
buff[offset + 18] = 0; // padding
|
|
||||||
buff[offset + 19] = 0; // padding
|
|
||||||
|
|
||||||
sock._sQlen += 20;
|
|
||||||
sock.flush();
|
sock.flush();
|
||||||
},
|
},
|
||||||
|
|
||||||
clientEncodings(sock, encodings) {
|
clientEncodings(sock, encodings) {
|
||||||
const buff = sock._sQ;
|
sock.sQpush8(2); // msg-type
|
||||||
const offset = sock._sQlen;
|
|
||||||
|
|
||||||
buff[offset] = 2; // msg-type
|
sock.sQpush8(0); // padding
|
||||||
buff[offset + 1] = 0; // padding
|
|
||||||
|
|
||||||
buff[offset + 2] = encodings.length >> 8;
|
sock.sQpush16(encodings.length);
|
||||||
buff[offset + 3] = encodings.length;
|
|
||||||
|
|
||||||
let j = offset + 4;
|
|
||||||
for (let i = 0; i < encodings.length; i++) {
|
for (let i = 0; i < encodings.length; i++) {
|
||||||
const enc = encodings[i];
|
sock.sQpush32(encodings[i]);
|
||||||
buff[j] = enc >> 24;
|
|
||||||
buff[j + 1] = enc >> 16;
|
|
||||||
buff[j + 2] = enc >> 8;
|
|
||||||
buff[j + 3] = enc;
|
|
||||||
|
|
||||||
j += 4;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sock._sQlen += j - offset;
|
|
||||||
sock.flush();
|
sock.flush();
|
||||||
},
|
},
|
||||||
|
|
||||||
fbUpdateRequest(sock, incremental, x, y, w, h) {
|
fbUpdateRequest(sock, incremental, x, y, w, h) {
|
||||||
const buff = sock._sQ;
|
|
||||||
const offset = sock._sQlen;
|
|
||||||
|
|
||||||
if (typeof(x) === "undefined") { x = 0; }
|
if (typeof(x) === "undefined") { x = 0; }
|
||||||
if (typeof(y) === "undefined") { y = 0; }
|
if (typeof(y) === "undefined") { y = 0; }
|
||||||
|
|
||||||
buff[offset] = 3; // msg-type
|
sock.sQpush8(3); // msg-type
|
||||||
buff[offset + 1] = incremental ? 1 : 0;
|
|
||||||
|
|
||||||
buff[offset + 2] = (x >> 8) & 0xFF;
|
sock.sQpush8(incremental ? 1 : 0);
|
||||||
buff[offset + 3] = x & 0xFF;
|
|
||||||
|
|
||||||
buff[offset + 4] = (y >> 8) & 0xFF;
|
sock.sQpush16(x);
|
||||||
buff[offset + 5] = y & 0xFF;
|
sock.sQpush16(y);
|
||||||
|
sock.sQpush16(w);
|
||||||
|
sock.sQpush16(h);
|
||||||
|
|
||||||
buff[offset + 6] = (w >> 8) & 0xFF;
|
|
||||||
buff[offset + 7] = w & 0xFF;
|
|
||||||
|
|
||||||
buff[offset + 8] = (h >> 8) & 0xFF;
|
|
||||||
buff[offset + 9] = h & 0xFF;
|
|
||||||
|
|
||||||
sock._sQlen += 10;
|
|
||||||
sock.flush();
|
sock.flush();
|
||||||
},
|
},
|
||||||
|
|
||||||
xvpOp(sock, ver, op) {
|
xvpOp(sock, ver, op) {
|
||||||
const buff = sock._sQ;
|
sock.sQpush8(250); // msg-type
|
||||||
const offset = sock._sQlen;
|
|
||||||
|
|
||||||
buff[offset] = 250; // msg-type
|
sock.sQpush8(0); // padding
|
||||||
buff[offset + 1] = 0; // padding
|
|
||||||
|
|
||||||
buff[offset + 2] = ver;
|
sock.sQpush8(ver);
|
||||||
buff[offset + 3] = op;
|
sock.sQpush8(op);
|
||||||
|
|
||||||
sock._sQlen += 4;
|
|
||||||
sock.flush();
|
sock.flush();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
128
core/websock.js
128
core/websock.js
|
|
@ -94,27 +94,7 @@ export default class Websock {
|
||||||
return "unknown";
|
return "unknown";
|
||||||
}
|
}
|
||||||
|
|
||||||
get sQ() {
|
|
||||||
return this._sQ;
|
|
||||||
}
|
|
||||||
|
|
||||||
get rQ() {
|
|
||||||
return this._rQ;
|
|
||||||
}
|
|
||||||
|
|
||||||
get rQi() {
|
|
||||||
return this._rQi;
|
|
||||||
}
|
|
||||||
|
|
||||||
set rQi(val) {
|
|
||||||
this._rQi = val;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Receive Queue
|
// Receive Queue
|
||||||
get rQlen() {
|
|
||||||
return this._rQlen - this._rQi;
|
|
||||||
}
|
|
||||||
|
|
||||||
rQpeek8() {
|
rQpeek8() {
|
||||||
return this._rQ[this._rQi];
|
return this._rQ[this._rQi];
|
||||||
}
|
}
|
||||||
|
|
@ -141,42 +121,47 @@ export default class Websock {
|
||||||
for (let byte = bytes - 1; byte >= 0; byte--) {
|
for (let byte = bytes - 1; byte >= 0; byte--) {
|
||||||
res += this._rQ[this._rQi++] << (byte * 8);
|
res += this._rQ[this._rQi++] << (byte * 8);
|
||||||
}
|
}
|
||||||
return res;
|
return res >>> 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
rQshiftStr(len) {
|
rQshiftStr(len) {
|
||||||
if (typeof(len) === 'undefined') { len = this.rQlen; }
|
|
||||||
let str = "";
|
let str = "";
|
||||||
// Handle large arrays in steps to avoid long strings on the stack
|
// Handle large arrays in steps to avoid long strings on the stack
|
||||||
for (let i = 0; i < len; i += 4096) {
|
for (let i = 0; i < len; i += 4096) {
|
||||||
let part = this.rQshiftBytes(Math.min(4096, len - i));
|
let part = this.rQshiftBytes(Math.min(4096, len - i), false);
|
||||||
str += String.fromCharCode.apply(null, part);
|
str += String.fromCharCode.apply(null, part);
|
||||||
}
|
}
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
rQshiftBytes(len) {
|
rQshiftBytes(len, copy=true) {
|
||||||
if (typeof(len) === 'undefined') { len = this.rQlen; }
|
|
||||||
this._rQi += len;
|
this._rQi += len;
|
||||||
return new Uint8Array(this._rQ.buffer, this._rQi - len, len);
|
if (copy) {
|
||||||
|
return this._rQ.slice(this._rQi - len, this._rQi);
|
||||||
|
} else {
|
||||||
|
return this._rQ.subarray(this._rQi - len, this._rQi);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rQshiftTo(target, len) {
|
rQshiftTo(target, len) {
|
||||||
if (len === undefined) { len = this.rQlen; }
|
|
||||||
// TODO: make this just use set with views when using a ArrayBuffer to store the rQ
|
// TODO: make this just use set with views when using a ArrayBuffer to store the rQ
|
||||||
target.set(new Uint8Array(this._rQ.buffer, this._rQi, len));
|
target.set(new Uint8Array(this._rQ.buffer, this._rQi, len));
|
||||||
this._rQi += len;
|
this._rQi += len;
|
||||||
}
|
}
|
||||||
|
|
||||||
rQslice(start, end = this.rQlen) {
|
rQpeekBytes(len, copy=true) {
|
||||||
return new Uint8Array(this._rQ.buffer, this._rQi + start, end - start);
|
if (copy) {
|
||||||
|
return this._rQ.slice(this._rQi, this._rQi + len);
|
||||||
|
} else {
|
||||||
|
return this._rQ.subarray(this._rQi, this._rQi + len);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check to see if we must wait for 'num' bytes (default to FBU.bytes)
|
// Check to see if we must wait for 'num' bytes (default to FBU.bytes)
|
||||||
// to be available in the receive queue. Return true if we need to
|
// to be available in the receive queue. Return true if we need to
|
||||||
// wait (and possibly print a debug message), otherwise false.
|
// wait (and possibly print a debug message), otherwise false.
|
||||||
rQwait(msg, num, goback) {
|
rQwait(msg, num, goback) {
|
||||||
if (this.rQlen < num) {
|
if (this._rQlen - this._rQi < num) {
|
||||||
if (goback) {
|
if (goback) {
|
||||||
if (this._rQi < goback) {
|
if (this._rQi < goback) {
|
||||||
throw new Error("rQwait cannot backup " + goback + " bytes");
|
throw new Error("rQwait cannot backup " + goback + " bytes");
|
||||||
|
|
@ -190,21 +175,56 @@ export default class Websock {
|
||||||
|
|
||||||
// Send Queue
|
// Send Queue
|
||||||
|
|
||||||
|
sQpush8(num) {
|
||||||
|
this._sQensureSpace(1);
|
||||||
|
this._sQ[this._sQlen++] = num;
|
||||||
|
}
|
||||||
|
|
||||||
|
sQpush16(num) {
|
||||||
|
this._sQensureSpace(2);
|
||||||
|
this._sQ[this._sQlen++] = (num >> 8) & 0xff;
|
||||||
|
this._sQ[this._sQlen++] = (num >> 0) & 0xff;
|
||||||
|
}
|
||||||
|
|
||||||
|
sQpush32(num) {
|
||||||
|
this._sQensureSpace(4);
|
||||||
|
this._sQ[this._sQlen++] = (num >> 24) & 0xff;
|
||||||
|
this._sQ[this._sQlen++] = (num >> 16) & 0xff;
|
||||||
|
this._sQ[this._sQlen++] = (num >> 8) & 0xff;
|
||||||
|
this._sQ[this._sQlen++] = (num >> 0) & 0xff;
|
||||||
|
}
|
||||||
|
|
||||||
|
sQpushString(str) {
|
||||||
|
let bytes = str.split('').map(chr => chr.charCodeAt(0));
|
||||||
|
this.sQpushBytes(new Uint8Array(bytes));
|
||||||
|
}
|
||||||
|
|
||||||
|
sQpushBytes(bytes) {
|
||||||
|
for (let offset = 0;offset < bytes.length;) {
|
||||||
|
this._sQensureSpace(1);
|
||||||
|
|
||||||
|
let chunkSize = this._sQbufferSize - this._sQlen;
|
||||||
|
if (chunkSize > bytes.length - offset) {
|
||||||
|
chunkSize = bytes.length - offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._sQ.set(bytes.subarray(offset, chunkSize), this._sQlen);
|
||||||
|
this._sQlen += chunkSize;
|
||||||
|
offset += chunkSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
flush() {
|
flush() {
|
||||||
if (this._sQlen > 0 && this.readyState === 'open') {
|
if (this._sQlen > 0 && this.readyState === 'open') {
|
||||||
this._websocket.send(this._encodeMessage());
|
this._websocket.send(new Uint8Array(this._sQ.buffer, 0, this._sQlen));
|
||||||
this._sQlen = 0;
|
this._sQlen = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
send(arr) {
|
_sQensureSpace(bytes) {
|
||||||
this._sQ.set(arr, this._sQlen);
|
if (this._sQbufferSize - this._sQlen < bytes) {
|
||||||
this._sQlen += arr.length;
|
this.flush();
|
||||||
this.flush();
|
}
|
||||||
}
|
|
||||||
|
|
||||||
sendString(str) {
|
|
||||||
this.send(str.split('').map(chr => chr.charCodeAt(0)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Event Handlers
|
// Event Handlers
|
||||||
|
|
@ -283,17 +303,12 @@ export default class Websock {
|
||||||
}
|
}
|
||||||
|
|
||||||
// private methods
|
// private methods
|
||||||
_encodeMessage() {
|
|
||||||
// Put in a binary arraybuffer
|
|
||||||
// according to the spec, you can send ArrayBufferViews with the send method
|
|
||||||
return new Uint8Array(this._sQ.buffer, 0, this._sQlen);
|
|
||||||
}
|
|
||||||
|
|
||||||
// We want to move all the unread data to the start of the queue,
|
// We want to move all the unread data to the start of the queue,
|
||||||
// e.g. compacting.
|
// e.g. compacting.
|
||||||
// The function also expands the receive que if needed, and for
|
// The function also expands the receive que if needed, and for
|
||||||
// performance reasons we combine these two actions to avoid
|
// performance reasons we combine these two actions to avoid
|
||||||
// unneccessary copying.
|
// unnecessary copying.
|
||||||
_expandCompactRQ(minFit) {
|
_expandCompactRQ(minFit) {
|
||||||
// if we're using less than 1/8th of the buffer even with the incoming bytes, compact in place
|
// if we're using less than 1/8th of the buffer even with the incoming bytes, compact in place
|
||||||
// instead of resizing
|
// instead of resizing
|
||||||
|
|
@ -309,7 +324,7 @@ export default class Websock {
|
||||||
// we don't want to grow unboundedly
|
// we don't want to grow unboundedly
|
||||||
if (this._rQbufferSize > MAX_RQ_GROW_SIZE) {
|
if (this._rQbufferSize > MAX_RQ_GROW_SIZE) {
|
||||||
this._rQbufferSize = MAX_RQ_GROW_SIZE;
|
this._rQbufferSize = MAX_RQ_GROW_SIZE;
|
||||||
if (this._rQbufferSize - this.rQlen < minFit) {
|
if (this._rQbufferSize - (this._rQlen - this._rQi) < minFit) {
|
||||||
throw new Error("Receive Queue buffer exceeded " + MAX_RQ_GROW_SIZE + " bytes, and the new message could not fit");
|
throw new Error("Receive Queue buffer exceeded " + MAX_RQ_GROW_SIZE + " bytes, and the new message could not fit");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -327,25 +342,22 @@ export default class Websock {
|
||||||
}
|
}
|
||||||
|
|
||||||
// push arraybuffer values onto the end of the receive que
|
// push arraybuffer values onto the end of the receive que
|
||||||
_DecodeMessage(data) {
|
_recvMessage(e) {
|
||||||
const u8 = new Uint8Array(data);
|
if (this._rQlen == this._rQi) {
|
||||||
|
// All data has now been processed, this means we
|
||||||
|
// can reset the receive queue.
|
||||||
|
this._rQlen = 0;
|
||||||
|
this._rQi = 0;
|
||||||
|
}
|
||||||
|
const u8 = new Uint8Array(e.data);
|
||||||
if (u8.length > this._rQbufferSize - this._rQlen) {
|
if (u8.length > this._rQbufferSize - this._rQlen) {
|
||||||
this._expandCompactRQ(u8.length);
|
this._expandCompactRQ(u8.length);
|
||||||
}
|
}
|
||||||
this._rQ.set(u8, this._rQlen);
|
this._rQ.set(u8, this._rQlen);
|
||||||
this._rQlen += u8.length;
|
this._rQlen += u8.length;
|
||||||
}
|
|
||||||
|
|
||||||
_recvMessage(e) {
|
if (this._rQlen - this._rQi > 0) {
|
||||||
this._DecodeMessage(e.data);
|
|
||||||
if (this.rQlen > 0) {
|
|
||||||
this._eventHandlers.message();
|
this._eventHandlers.message();
|
||||||
if (this._rQlen == this._rQi) {
|
|
||||||
// All data has now been processed, this means we
|
|
||||||
// can reset the receive queue.
|
|
||||||
this._rQlen = 0;
|
|
||||||
this._rQi = 0;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
Log.Debug("Ignoring empty message");
|
Log.Debug("Ignoring empty message");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -81,9 +81,3 @@ None
|
||||||
| blitImage | (x, y, width, height, arr, offset, from_queue) | Blit pixels (of R,G,B,A) to the display
|
| blitImage | (x, y, width, height, arr, offset, from_queue) | Blit pixels (of R,G,B,A) to the display
|
||||||
| drawImage | (img, x, y) | Draw image and track damage
|
| drawImage | (img, x, y) | Draw image and track damage
|
||||||
| autoscale | (containerWidth, containerHeight) | Scale the display
|
| autoscale | (containerWidth, containerHeight) | Scale the display
|
||||||
|
|
||||||
### 2.2.3 Callbacks
|
|
||||||
|
|
||||||
| name | parameters | description
|
|
||||||
| ------- | ---------- | ------------
|
|
||||||
| onflush | () | A display flush has been requested and we are now ready to resume FBU processing
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@novnc/novnc",
|
"name": "@novnc/novnc",
|
||||||
"version": "1.4.0-beta",
|
"version": "1.4.0",
|
||||||
"description": "An HTML5 VNC client",
|
"description": "An HTML5 VNC client",
|
||||||
"browser": "lib/rfb",
|
"browser": "lib/rfb",
|
||||||
"directories": {
|
"directories": {
|
||||||
|
|
|
||||||
199
po/ja.po
199
po/ja.po
|
|
@ -8,8 +8,8 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: noVNC 1.1.0\n"
|
"Project-Id-Version: noVNC 1.1.0\n"
|
||||||
"Report-Msgid-Bugs-To: novnc@googlegroups.com\n"
|
"Report-Msgid-Bugs-To: novnc@googlegroups.com\n"
|
||||||
"POT-Creation-Date: 2020-07-03 16:11+0200\n"
|
"POT-Creation-Date: 2022-12-27 15:24+0100\n"
|
||||||
"PO-Revision-Date: 2021-01-15 12:37+0900\n"
|
"PO-Revision-Date: 2023-03-21 12:42+0900\n"
|
||||||
"Last-Translator: nnn1590 <nnn1590@nnn1590.org>\n"
|
"Last-Translator: nnn1590 <nnn1590@nnn1590.org>\n"
|
||||||
"Language-Team: Japanese\n"
|
"Language-Team: Japanese\n"
|
||||||
"Language: ja\n"
|
"Language: ja\n"
|
||||||
|
|
@ -19,286 +19,325 @@ msgstr ""
|
||||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||||
"X-Generator: Poedit 2.3\n"
|
"X-Generator: Poedit 2.3\n"
|
||||||
|
|
||||||
#: ../app/ui.js:394
|
#: ../app/ui.js:69
|
||||||
|
msgid "HTTPS is required for full functionality"
|
||||||
|
msgstr "すべての機能を使用するにはHTTPS接続が必要です"
|
||||||
|
|
||||||
|
#: ../app/ui.js:410
|
||||||
msgid "Connecting..."
|
msgid "Connecting..."
|
||||||
msgstr "接続しています..."
|
msgstr "接続しています..."
|
||||||
|
|
||||||
#: ../app/ui.js:401
|
#: ../app/ui.js:417
|
||||||
msgid "Disconnecting..."
|
msgid "Disconnecting..."
|
||||||
msgstr "切断しています..."
|
msgstr "切断しています..."
|
||||||
|
|
||||||
#: ../app/ui.js:407
|
#: ../app/ui.js:423
|
||||||
msgid "Reconnecting..."
|
msgid "Reconnecting..."
|
||||||
msgstr "再接続しています..."
|
msgstr "再接続しています..."
|
||||||
|
|
||||||
#: ../app/ui.js:412
|
#: ../app/ui.js:428
|
||||||
msgid "Internal error"
|
msgid "Internal error"
|
||||||
msgstr "内部エラー"
|
msgstr "内部エラー"
|
||||||
|
|
||||||
#: ../app/ui.js:1008
|
#: ../app/ui.js:1026
|
||||||
msgid "Must set host"
|
msgid "Must set host"
|
||||||
msgstr "ホストを設定する必要があります"
|
msgstr "ホストを設定する必要があります"
|
||||||
|
|
||||||
#: ../app/ui.js:1090
|
#: ../app/ui.js:1110
|
||||||
msgid "Connected (encrypted) to "
|
msgid "Connected (encrypted) to "
|
||||||
msgstr "接続しました (暗号化済み): "
|
msgstr "接続しました (暗号化済み): "
|
||||||
|
|
||||||
#: ../app/ui.js:1092
|
#: ../app/ui.js:1112
|
||||||
msgid "Connected (unencrypted) to "
|
msgid "Connected (unencrypted) to "
|
||||||
msgstr "接続しました (暗号化されていません): "
|
msgstr "接続しました (暗号化されていません): "
|
||||||
|
|
||||||
#: ../app/ui.js:1115
|
#: ../app/ui.js:1135
|
||||||
msgid "Something went wrong, connection is closed"
|
msgid "Something went wrong, connection is closed"
|
||||||
msgstr "何らかの問題で、接続が閉じられました"
|
msgstr "何らかの問題で、接続が閉じられました"
|
||||||
|
|
||||||
#: ../app/ui.js:1118
|
#: ../app/ui.js:1138
|
||||||
msgid "Failed to connect to server"
|
msgid "Failed to connect to server"
|
||||||
msgstr "サーバーへの接続に失敗しました"
|
msgstr "サーバーへの接続に失敗しました"
|
||||||
|
|
||||||
#: ../app/ui.js:1128
|
#: ../app/ui.js:1150
|
||||||
msgid "Disconnected"
|
msgid "Disconnected"
|
||||||
msgstr "切断しました"
|
msgstr "切断しました"
|
||||||
|
|
||||||
#: ../app/ui.js:1143
|
#: ../app/ui.js:1165
|
||||||
msgid "New connection has been rejected with reason: "
|
msgid "New connection has been rejected with reason: "
|
||||||
msgstr "新規接続は次の理由で拒否されました: "
|
msgstr "新規接続は次の理由で拒否されました: "
|
||||||
|
|
||||||
#: ../app/ui.js:1146
|
#: ../app/ui.js:1168
|
||||||
msgid "New connection has been rejected"
|
msgid "New connection has been rejected"
|
||||||
msgstr "新規接続は拒否されました"
|
msgstr "新規接続は拒否されました"
|
||||||
|
|
||||||
#: ../app/ui.js:1181
|
#: ../app/ui.js:1234
|
||||||
msgid "Credentials are required"
|
msgid "Credentials are required"
|
||||||
msgstr "資格情報が必要です"
|
msgstr "資格情報が必要です"
|
||||||
|
|
||||||
#: ../vnc.html:74
|
#: ../vnc.html:57
|
||||||
msgid "noVNC encountered an error:"
|
msgid "noVNC encountered an error:"
|
||||||
msgstr "noVNC でエラーが発生しました:"
|
msgstr "noVNC でエラーが発生しました:"
|
||||||
|
|
||||||
#: ../vnc.html:84
|
#: ../vnc.html:67
|
||||||
msgid "Hide/Show the control bar"
|
msgid "Hide/Show the control bar"
|
||||||
msgstr "コントロールバーを隠す/表示する"
|
msgstr "コントロールバーを隠す/表示する"
|
||||||
|
|
||||||
#: ../vnc.html:91
|
#: ../vnc.html:76
|
||||||
msgid "Drag"
|
msgid "Drag"
|
||||||
msgstr "ドラッグ"
|
msgstr "ドラッグ"
|
||||||
|
|
||||||
#: ../vnc.html:91
|
#: ../vnc.html:76
|
||||||
msgid "Move/Drag Viewport"
|
msgid "Move/Drag Viewport"
|
||||||
msgstr "ビューポートを移動/ドラッグ"
|
msgstr "ビューポートを移動/ドラッグ"
|
||||||
|
|
||||||
#: ../vnc.html:97
|
#: ../vnc.html:82
|
||||||
msgid "Keyboard"
|
msgid "Keyboard"
|
||||||
msgstr "キーボード"
|
msgstr "キーボード"
|
||||||
|
|
||||||
#: ../vnc.html:97
|
#: ../vnc.html:82
|
||||||
msgid "Show Keyboard"
|
msgid "Show Keyboard"
|
||||||
msgstr "キーボードを表示"
|
msgstr "キーボードを表示"
|
||||||
|
|
||||||
#: ../vnc.html:102
|
#: ../vnc.html:87
|
||||||
msgid "Extra keys"
|
msgid "Extra keys"
|
||||||
msgstr "追加キー"
|
msgstr "追加キー"
|
||||||
|
|
||||||
#: ../vnc.html:102
|
#: ../vnc.html:87
|
||||||
msgid "Show Extra Keys"
|
msgid "Show Extra Keys"
|
||||||
msgstr "追加キーを表示"
|
msgstr "追加キーを表示"
|
||||||
|
|
||||||
#: ../vnc.html:107
|
#: ../vnc.html:92
|
||||||
msgid "Ctrl"
|
msgid "Ctrl"
|
||||||
msgstr "Ctrl"
|
msgstr "Ctrl"
|
||||||
|
|
||||||
#: ../vnc.html:107
|
#: ../vnc.html:92
|
||||||
msgid "Toggle Ctrl"
|
msgid "Toggle Ctrl"
|
||||||
msgstr "Ctrl キーを切り替え"
|
msgstr "Ctrl キーをトグル"
|
||||||
|
|
||||||
#: ../vnc.html:110
|
#: ../vnc.html:95
|
||||||
msgid "Alt"
|
msgid "Alt"
|
||||||
msgstr "Alt"
|
msgstr "Alt"
|
||||||
|
|
||||||
#: ../vnc.html:110
|
#: ../vnc.html:95
|
||||||
msgid "Toggle Alt"
|
msgid "Toggle Alt"
|
||||||
msgstr "Alt キーを切り替え"
|
msgstr "Alt キーをトグル"
|
||||||
|
|
||||||
#: ../vnc.html:113
|
#: ../vnc.html:98
|
||||||
msgid "Toggle Windows"
|
msgid "Toggle Windows"
|
||||||
msgstr "Windows キーを切り替え"
|
msgstr "Windows キーをトグル"
|
||||||
|
|
||||||
#: ../vnc.html:113
|
#: ../vnc.html:98
|
||||||
msgid "Windows"
|
msgid "Windows"
|
||||||
msgstr "Windows"
|
msgstr "Windows"
|
||||||
|
|
||||||
#: ../vnc.html:116
|
#: ../vnc.html:101
|
||||||
msgid "Send Tab"
|
msgid "Send Tab"
|
||||||
msgstr "Tab キーを送信"
|
msgstr "Tab キーを送信"
|
||||||
|
|
||||||
#: ../vnc.html:116
|
#: ../vnc.html:101
|
||||||
msgid "Tab"
|
msgid "Tab"
|
||||||
msgstr "Tab"
|
msgstr "Tab"
|
||||||
|
|
||||||
#: ../vnc.html:119
|
#: ../vnc.html:104
|
||||||
msgid "Esc"
|
msgid "Esc"
|
||||||
msgstr "Esc"
|
msgstr "Esc"
|
||||||
|
|
||||||
#: ../vnc.html:119
|
#: ../vnc.html:104
|
||||||
msgid "Send Escape"
|
msgid "Send Escape"
|
||||||
msgstr "Escape キーを送信"
|
msgstr "Escape キーを送信"
|
||||||
|
|
||||||
#: ../vnc.html:122
|
#: ../vnc.html:107
|
||||||
msgid "Ctrl+Alt+Del"
|
msgid "Ctrl+Alt+Del"
|
||||||
msgstr "Ctrl+Alt+Del"
|
msgstr "Ctrl+Alt+Del"
|
||||||
|
|
||||||
#: ../vnc.html:122
|
#: ../vnc.html:107
|
||||||
msgid "Send Ctrl-Alt-Del"
|
msgid "Send Ctrl-Alt-Del"
|
||||||
msgstr "Ctrl-Alt-Del を送信"
|
msgstr "Ctrl-Alt-Del を送信"
|
||||||
|
|
||||||
#: ../vnc.html:129
|
#: ../vnc.html:114
|
||||||
msgid "Shutdown/Reboot"
|
msgid "Shutdown/Reboot"
|
||||||
msgstr "シャットダウン/再起動"
|
msgstr "シャットダウン/再起動"
|
||||||
|
|
||||||
#: ../vnc.html:129
|
#: ../vnc.html:114
|
||||||
msgid "Shutdown/Reboot..."
|
msgid "Shutdown/Reboot..."
|
||||||
msgstr "シャットダウン/再起動..."
|
msgstr "シャットダウン/再起動..."
|
||||||
|
|
||||||
#: ../vnc.html:135
|
#: ../vnc.html:120
|
||||||
msgid "Power"
|
msgid "Power"
|
||||||
msgstr "電源"
|
msgstr "電源"
|
||||||
|
|
||||||
#: ../vnc.html:137
|
#: ../vnc.html:122
|
||||||
msgid "Shutdown"
|
msgid "Shutdown"
|
||||||
msgstr "シャットダウン"
|
msgstr "シャットダウン"
|
||||||
|
|
||||||
#: ../vnc.html:138
|
#: ../vnc.html:123
|
||||||
msgid "Reboot"
|
msgid "Reboot"
|
||||||
msgstr "再起動"
|
msgstr "再起動"
|
||||||
|
|
||||||
#: ../vnc.html:139
|
#: ../vnc.html:124
|
||||||
msgid "Reset"
|
msgid "Reset"
|
||||||
msgstr "リセット"
|
msgstr "リセット"
|
||||||
|
|
||||||
#: ../vnc.html:144 ../vnc.html:150
|
#: ../vnc.html:129 ../vnc.html:135
|
||||||
msgid "Clipboard"
|
msgid "Clipboard"
|
||||||
msgstr "クリップボード"
|
msgstr "クリップボード"
|
||||||
|
|
||||||
#: ../vnc.html:154
|
#: ../vnc.html:137
|
||||||
msgid "Clear"
|
msgid "Edit clipboard content in the textarea below."
|
||||||
msgstr "クリア"
|
msgstr "以下の入力欄からクリップボードの内容を編集できます。"
|
||||||
|
|
||||||
#: ../vnc.html:160
|
#: ../vnc.html:145
|
||||||
msgid "Fullscreen"
|
msgid "Full Screen"
|
||||||
msgstr "全画面表示"
|
msgstr "全画面表示"
|
||||||
|
|
||||||
#: ../vnc.html:165 ../vnc.html:172
|
#: ../vnc.html:150 ../vnc.html:156
|
||||||
msgid "Settings"
|
msgid "Settings"
|
||||||
msgstr "設定"
|
msgstr "設定"
|
||||||
|
|
||||||
#: ../vnc.html:175
|
#: ../vnc.html:160
|
||||||
msgid "Shared Mode"
|
msgid "Shared Mode"
|
||||||
msgstr "共有モード"
|
msgstr "共有モード"
|
||||||
|
|
||||||
#: ../vnc.html:178
|
#: ../vnc.html:163
|
||||||
msgid "View Only"
|
msgid "View Only"
|
||||||
msgstr "表示のみ"
|
msgstr "表示専用"
|
||||||
|
|
||||||
#: ../vnc.html:182
|
#: ../vnc.html:167
|
||||||
msgid "Clip to Window"
|
msgid "Clip to Window"
|
||||||
msgstr "ウィンドウにクリップ"
|
msgstr "ウィンドウにクリップ"
|
||||||
|
|
||||||
#: ../vnc.html:185
|
#: ../vnc.html:170
|
||||||
msgid "Scaling Mode:"
|
msgid "Scaling Mode:"
|
||||||
msgstr "スケーリングモード:"
|
msgstr "スケーリングモード:"
|
||||||
|
|
||||||
#: ../vnc.html:187
|
#: ../vnc.html:172
|
||||||
msgid "None"
|
msgid "None"
|
||||||
msgstr "なし"
|
msgstr "なし"
|
||||||
|
|
||||||
#: ../vnc.html:188
|
#: ../vnc.html:173
|
||||||
msgid "Local Scaling"
|
msgid "Local Scaling"
|
||||||
msgstr "ローカルスケーリング"
|
msgstr "ローカルスケーリング"
|
||||||
|
|
||||||
#: ../vnc.html:189
|
#: ../vnc.html:174
|
||||||
msgid "Remote Resizing"
|
msgid "Remote Resizing"
|
||||||
msgstr "リモートでリサイズ"
|
msgstr "リモートでリサイズ"
|
||||||
|
|
||||||
#: ../vnc.html:194
|
#: ../vnc.html:179
|
||||||
msgid "Advanced"
|
msgid "Advanced"
|
||||||
msgstr "高度"
|
msgstr "高度"
|
||||||
|
|
||||||
#: ../vnc.html:197
|
#: ../vnc.html:182
|
||||||
msgid "Quality:"
|
msgid "Quality:"
|
||||||
msgstr "品質:"
|
msgstr "品質:"
|
||||||
|
|
||||||
#: ../vnc.html:201
|
#: ../vnc.html:186
|
||||||
msgid "Compression level:"
|
msgid "Compression level:"
|
||||||
msgstr "圧縮レベル:"
|
msgstr "圧縮レベル:"
|
||||||
|
|
||||||
#: ../vnc.html:206
|
#: ../vnc.html:191
|
||||||
msgid "Repeater ID:"
|
msgid "Repeater ID:"
|
||||||
msgstr "リピーター ID:"
|
msgstr "リピーター ID:"
|
||||||
|
|
||||||
#: ../vnc.html:210
|
#: ../vnc.html:195
|
||||||
msgid "WebSocket"
|
msgid "WebSocket"
|
||||||
msgstr "WebSocket"
|
msgstr "WebSocket"
|
||||||
|
|
||||||
#: ../vnc.html:213
|
#: ../vnc.html:198
|
||||||
msgid "Encrypt"
|
msgid "Encrypt"
|
||||||
msgstr "暗号化"
|
msgstr "暗号化"
|
||||||
|
|
||||||
#: ../vnc.html:216
|
#: ../vnc.html:201
|
||||||
msgid "Host:"
|
msgid "Host:"
|
||||||
msgstr "ホスト:"
|
msgstr "ホスト:"
|
||||||
|
|
||||||
#: ../vnc.html:220
|
#: ../vnc.html:205
|
||||||
msgid "Port:"
|
msgid "Port:"
|
||||||
msgstr "ポート:"
|
msgstr "ポート:"
|
||||||
|
|
||||||
#: ../vnc.html:224
|
#: ../vnc.html:209
|
||||||
msgid "Path:"
|
msgid "Path:"
|
||||||
msgstr "パス:"
|
msgstr "パス:"
|
||||||
|
|
||||||
#: ../vnc.html:231
|
#: ../vnc.html:216
|
||||||
msgid "Automatic Reconnect"
|
msgid "Automatic Reconnect"
|
||||||
msgstr "自動再接続"
|
msgstr "自動再接続"
|
||||||
|
|
||||||
#: ../vnc.html:234
|
#: ../vnc.html:219
|
||||||
msgid "Reconnect Delay (ms):"
|
msgid "Reconnect Delay (ms):"
|
||||||
msgstr "再接続する遅延 (ミリ秒):"
|
msgstr "再接続する遅延 (ミリ秒):"
|
||||||
|
|
||||||
#: ../vnc.html:239
|
#: ../vnc.html:224
|
||||||
msgid "Show Dot when No Cursor"
|
msgid "Show Dot when No Cursor"
|
||||||
msgstr "カーソルがないときにドットを表示"
|
msgstr "カーソルがないときにドットを表示する"
|
||||||
|
|
||||||
#: ../vnc.html:244
|
#: ../vnc.html:229
|
||||||
msgid "Logging:"
|
msgid "Logging:"
|
||||||
msgstr "ロギング:"
|
msgstr "ロギング:"
|
||||||
|
|
||||||
#: ../vnc.html:253
|
#: ../vnc.html:238
|
||||||
msgid "Version:"
|
msgid "Version:"
|
||||||
msgstr "バージョン:"
|
msgstr "バージョン:"
|
||||||
|
|
||||||
#: ../vnc.html:261
|
#: ../vnc.html:246
|
||||||
msgid "Disconnect"
|
msgid "Disconnect"
|
||||||
msgstr "切断"
|
msgstr "切断"
|
||||||
|
|
||||||
#: ../vnc.html:280
|
#: ../vnc.html:269
|
||||||
msgid "Connect"
|
msgid "Connect"
|
||||||
msgstr "接続"
|
msgstr "接続"
|
||||||
|
|
||||||
#: ../vnc.html:290
|
#: ../vnc.html:278
|
||||||
|
msgid "Server identity"
|
||||||
|
msgstr "サーバーの識別情報"
|
||||||
|
|
||||||
|
#: ../vnc.html:281
|
||||||
|
msgid "The server has provided the following identifying information:"
|
||||||
|
msgstr "サーバーは以下の識別情報を提供しています:"
|
||||||
|
|
||||||
|
#: ../vnc.html:285
|
||||||
|
msgid "Fingerprint:"
|
||||||
|
msgstr "フィンガープリント:"
|
||||||
|
|
||||||
|
#: ../vnc.html:288
|
||||||
|
msgid ""
|
||||||
|
"Please verify that the information is correct and press \"Approve\". "
|
||||||
|
"Otherwise press \"Reject\"."
|
||||||
|
msgstr ""
|
||||||
|
"この情報が正しい場合は「承認」を、そうでない場合は「拒否」を押してく"
|
||||||
|
"ださい。"
|
||||||
|
|
||||||
|
#: ../vnc.html:293
|
||||||
|
msgid "Approve"
|
||||||
|
msgstr "承認"
|
||||||
|
|
||||||
|
#: ../vnc.html:294
|
||||||
|
msgid "Reject"
|
||||||
|
msgstr "拒否"
|
||||||
|
|
||||||
|
#: ../vnc.html:302
|
||||||
|
msgid "Credentials"
|
||||||
|
msgstr "資格情報"
|
||||||
|
|
||||||
|
#: ../vnc.html:306
|
||||||
msgid "Username:"
|
msgid "Username:"
|
||||||
msgstr "ユーザー名:"
|
msgstr "ユーザー名:"
|
||||||
|
|
||||||
#: ../vnc.html:294
|
#: ../vnc.html:310
|
||||||
msgid "Password:"
|
msgid "Password:"
|
||||||
msgstr "パスワード:"
|
msgstr "パスワード:"
|
||||||
|
|
||||||
#: ../vnc.html:298
|
#: ../vnc.html:314
|
||||||
msgid "Send Credentials"
|
msgid "Send Credentials"
|
||||||
msgstr "資格情報を送信"
|
msgstr "資格情報を送信"
|
||||||
|
|
||||||
#: ../vnc.html:308
|
#: ../vnc.html:323
|
||||||
msgid "Cancel"
|
msgid "Cancel"
|
||||||
msgstr "キャンセル"
|
msgstr "キャンセル"
|
||||||
|
|
||||||
|
#~ msgid "Clear"
|
||||||
|
#~ msgstr "クリア"
|
||||||
|
|
||||||
#~ msgid "Password is required"
|
#~ msgid "Password is required"
|
||||||
#~ msgstr "パスワードが必要です"
|
#~ msgstr "パスワードが必要です"
|
||||||
|
|
||||||
|
|
|
||||||
189
po/sv.po
189
po/sv.po
|
|
@ -8,8 +8,8 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: noVNC 1.3.0\n"
|
"Project-Id-Version: noVNC 1.3.0\n"
|
||||||
"Report-Msgid-Bugs-To: novnc@googlegroups.com\n"
|
"Report-Msgid-Bugs-To: novnc@googlegroups.com\n"
|
||||||
"POT-Creation-Date: 2021-08-27 16:03+0200\n"
|
"POT-Creation-Date: 2023-01-20 12:54+0100\n"
|
||||||
"PO-Revision-Date: 2021-08-27 16:18+0200\n"
|
"PO-Revision-Date: 2023-01-20 12:58+0100\n"
|
||||||
"Last-Translator: Samuel Mannehed <samuel@cendio.se>\n"
|
"Last-Translator: Samuel Mannehed <samuel@cendio.se>\n"
|
||||||
"Language-Team: none\n"
|
"Language-Team: none\n"
|
||||||
"Language: sv\n"
|
"Language: sv\n"
|
||||||
|
|
@ -17,265 +17,269 @@ msgstr ""
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
"X-Generator: Poedit 2.0.3\n"
|
"X-Generator: Poedit 3.2.2\n"
|
||||||
|
|
||||||
#: ../app/ui.js:400
|
#: ../app/ui.js:69
|
||||||
|
msgid "HTTPS is required for full functionality"
|
||||||
|
msgstr "HTTPS krävs för full funktionalitet"
|
||||||
|
|
||||||
|
#: ../app/ui.js:410
|
||||||
msgid "Connecting..."
|
msgid "Connecting..."
|
||||||
msgstr "Ansluter..."
|
msgstr "Ansluter..."
|
||||||
|
|
||||||
#: ../app/ui.js:407
|
#: ../app/ui.js:417
|
||||||
msgid "Disconnecting..."
|
msgid "Disconnecting..."
|
||||||
msgstr "Kopplar ner..."
|
msgstr "Kopplar ner..."
|
||||||
|
|
||||||
#: ../app/ui.js:413
|
#: ../app/ui.js:423
|
||||||
msgid "Reconnecting..."
|
msgid "Reconnecting..."
|
||||||
msgstr "Återansluter..."
|
msgstr "Återansluter..."
|
||||||
|
|
||||||
#: ../app/ui.js:418
|
#: ../app/ui.js:428
|
||||||
msgid "Internal error"
|
msgid "Internal error"
|
||||||
msgstr "Internt fel"
|
msgstr "Internt fel"
|
||||||
|
|
||||||
#: ../app/ui.js:1009
|
#: ../app/ui.js:1026
|
||||||
msgid "Must set host"
|
msgid "Must set host"
|
||||||
msgstr "Du måste specifiera en värd"
|
msgstr "Du måste specifiera en värd"
|
||||||
|
|
||||||
#: ../app/ui.js:1091
|
#: ../app/ui.js:1110
|
||||||
msgid "Connected (encrypted) to "
|
msgid "Connected (encrypted) to "
|
||||||
msgstr "Ansluten (krypterat) till "
|
msgstr "Ansluten (krypterat) till "
|
||||||
|
|
||||||
#: ../app/ui.js:1093
|
#: ../app/ui.js:1112
|
||||||
msgid "Connected (unencrypted) to "
|
msgid "Connected (unencrypted) to "
|
||||||
msgstr "Ansluten (okrypterat) till "
|
msgstr "Ansluten (okrypterat) till "
|
||||||
|
|
||||||
#: ../app/ui.js:1116
|
#: ../app/ui.js:1135
|
||||||
msgid "Something went wrong, connection is closed"
|
msgid "Something went wrong, connection is closed"
|
||||||
msgstr "Något gick fel, anslutningen avslutades"
|
msgstr "Något gick fel, anslutningen avslutades"
|
||||||
|
|
||||||
#: ../app/ui.js:1119
|
#: ../app/ui.js:1138
|
||||||
msgid "Failed to connect to server"
|
msgid "Failed to connect to server"
|
||||||
msgstr "Misslyckades att ansluta till servern"
|
msgstr "Misslyckades att ansluta till servern"
|
||||||
|
|
||||||
#: ../app/ui.js:1129
|
#: ../app/ui.js:1150
|
||||||
msgid "Disconnected"
|
msgid "Disconnected"
|
||||||
msgstr "Frånkopplad"
|
msgstr "Frånkopplad"
|
||||||
|
|
||||||
#: ../app/ui.js:1144
|
#: ../app/ui.js:1165
|
||||||
msgid "New connection has been rejected with reason: "
|
msgid "New connection has been rejected with reason: "
|
||||||
msgstr "Ny anslutning har blivit nekad med följande skäl: "
|
msgstr "Ny anslutning har blivit nekad med följande skäl: "
|
||||||
|
|
||||||
#: ../app/ui.js:1147
|
#: ../app/ui.js:1168
|
||||||
msgid "New connection has been rejected"
|
msgid "New connection has been rejected"
|
||||||
msgstr "Ny anslutning har blivit nekad"
|
msgstr "Ny anslutning har blivit nekad"
|
||||||
|
|
||||||
#: ../app/ui.js:1182
|
#: ../app/ui.js:1234
|
||||||
msgid "Credentials are required"
|
msgid "Credentials are required"
|
||||||
msgstr "Användaruppgifter krävs"
|
msgstr "Användaruppgifter krävs"
|
||||||
|
|
||||||
#: ../vnc.html:61
|
#: ../vnc.html:55
|
||||||
msgid "noVNC encountered an error:"
|
msgid "noVNC encountered an error:"
|
||||||
msgstr "noVNC stötte på ett problem:"
|
msgstr "noVNC stötte på ett problem:"
|
||||||
|
|
||||||
#: ../vnc.html:71
|
#: ../vnc.html:65
|
||||||
msgid "Hide/Show the control bar"
|
msgid "Hide/Show the control bar"
|
||||||
msgstr "Göm/Visa kontrollbaren"
|
msgstr "Göm/Visa kontrollbaren"
|
||||||
|
|
||||||
#: ../vnc.html:78
|
#: ../vnc.html:74
|
||||||
msgid "Drag"
|
msgid "Drag"
|
||||||
msgstr "Dra"
|
msgstr "Dra"
|
||||||
|
|
||||||
#: ../vnc.html:78
|
#: ../vnc.html:74
|
||||||
msgid "Move/Drag Viewport"
|
msgid "Move/Drag Viewport"
|
||||||
msgstr "Flytta/Dra Vyn"
|
msgstr "Flytta/Dra Vyn"
|
||||||
|
|
||||||
#: ../vnc.html:84
|
#: ../vnc.html:80
|
||||||
msgid "Keyboard"
|
msgid "Keyboard"
|
||||||
msgstr "Tangentbord"
|
msgstr "Tangentbord"
|
||||||
|
|
||||||
#: ../vnc.html:84
|
#: ../vnc.html:80
|
||||||
msgid "Show Keyboard"
|
msgid "Show Keyboard"
|
||||||
msgstr "Visa Tangentbord"
|
msgstr "Visa Tangentbord"
|
||||||
|
|
||||||
#: ../vnc.html:89
|
#: ../vnc.html:85
|
||||||
msgid "Extra keys"
|
msgid "Extra keys"
|
||||||
msgstr "Extraknappar"
|
msgstr "Extraknappar"
|
||||||
|
|
||||||
#: ../vnc.html:89
|
#: ../vnc.html:85
|
||||||
msgid "Show Extra Keys"
|
msgid "Show Extra Keys"
|
||||||
msgstr "Visa Extraknappar"
|
msgstr "Visa Extraknappar"
|
||||||
|
|
||||||
#: ../vnc.html:94
|
#: ../vnc.html:90
|
||||||
msgid "Ctrl"
|
msgid "Ctrl"
|
||||||
msgstr "Ctrl"
|
msgstr "Ctrl"
|
||||||
|
|
||||||
#: ../vnc.html:94
|
#: ../vnc.html:90
|
||||||
msgid "Toggle Ctrl"
|
msgid "Toggle Ctrl"
|
||||||
msgstr "Växla Ctrl"
|
msgstr "Växla Ctrl"
|
||||||
|
|
||||||
#: ../vnc.html:97
|
#: ../vnc.html:93
|
||||||
msgid "Alt"
|
msgid "Alt"
|
||||||
msgstr "Alt"
|
msgstr "Alt"
|
||||||
|
|
||||||
#: ../vnc.html:97
|
#: ../vnc.html:93
|
||||||
msgid "Toggle Alt"
|
msgid "Toggle Alt"
|
||||||
msgstr "Växla Alt"
|
msgstr "Växla Alt"
|
||||||
|
|
||||||
#: ../vnc.html:100
|
#: ../vnc.html:96
|
||||||
msgid "Toggle Windows"
|
msgid "Toggle Windows"
|
||||||
msgstr "Växla Windows"
|
msgstr "Växla Windows"
|
||||||
|
|
||||||
#: ../vnc.html:100
|
#: ../vnc.html:96
|
||||||
msgid "Windows"
|
msgid "Windows"
|
||||||
msgstr "Windows"
|
msgstr "Windows"
|
||||||
|
|
||||||
#: ../vnc.html:103
|
#: ../vnc.html:99
|
||||||
msgid "Send Tab"
|
msgid "Send Tab"
|
||||||
msgstr "Skicka Tab"
|
msgstr "Skicka Tab"
|
||||||
|
|
||||||
#: ../vnc.html:103
|
#: ../vnc.html:99
|
||||||
msgid "Tab"
|
msgid "Tab"
|
||||||
msgstr "Tab"
|
msgstr "Tab"
|
||||||
|
|
||||||
#: ../vnc.html:106
|
#: ../vnc.html:102
|
||||||
msgid "Esc"
|
msgid "Esc"
|
||||||
msgstr "Esc"
|
msgstr "Esc"
|
||||||
|
|
||||||
#: ../vnc.html:106
|
#: ../vnc.html:102
|
||||||
msgid "Send Escape"
|
msgid "Send Escape"
|
||||||
msgstr "Skicka Escape"
|
msgstr "Skicka Escape"
|
||||||
|
|
||||||
#: ../vnc.html:109
|
#: ../vnc.html:105
|
||||||
msgid "Ctrl+Alt+Del"
|
msgid "Ctrl+Alt+Del"
|
||||||
msgstr "Ctrl+Alt+Del"
|
msgstr "Ctrl+Alt+Del"
|
||||||
|
|
||||||
#: ../vnc.html:109
|
#: ../vnc.html:105
|
||||||
msgid "Send Ctrl-Alt-Del"
|
msgid "Send Ctrl-Alt-Del"
|
||||||
msgstr "Skicka Ctrl-Alt-Del"
|
msgstr "Skicka Ctrl-Alt-Del"
|
||||||
|
|
||||||
#: ../vnc.html:116
|
#: ../vnc.html:112
|
||||||
msgid "Shutdown/Reboot"
|
msgid "Shutdown/Reboot"
|
||||||
msgstr "Stäng av/Boota om"
|
msgstr "Stäng av/Boota om"
|
||||||
|
|
||||||
#: ../vnc.html:116
|
#: ../vnc.html:112
|
||||||
msgid "Shutdown/Reboot..."
|
msgid "Shutdown/Reboot..."
|
||||||
msgstr "Stäng av/Boota om..."
|
msgstr "Stäng av/Boota om..."
|
||||||
|
|
||||||
#: ../vnc.html:122
|
#: ../vnc.html:118
|
||||||
msgid "Power"
|
msgid "Power"
|
||||||
msgstr "Ström"
|
msgstr "Ström"
|
||||||
|
|
||||||
#: ../vnc.html:124
|
#: ../vnc.html:120
|
||||||
msgid "Shutdown"
|
msgid "Shutdown"
|
||||||
msgstr "Stäng av"
|
msgstr "Stäng av"
|
||||||
|
|
||||||
#: ../vnc.html:125
|
#: ../vnc.html:121
|
||||||
msgid "Reboot"
|
msgid "Reboot"
|
||||||
msgstr "Boota om"
|
msgstr "Boota om"
|
||||||
|
|
||||||
#: ../vnc.html:126
|
#: ../vnc.html:122
|
||||||
msgid "Reset"
|
msgid "Reset"
|
||||||
msgstr "Återställ"
|
msgstr "Återställ"
|
||||||
|
|
||||||
#: ../vnc.html:131 ../vnc.html:137
|
#: ../vnc.html:127 ../vnc.html:133
|
||||||
msgid "Clipboard"
|
msgid "Clipboard"
|
||||||
msgstr "Urklipp"
|
msgstr "Urklipp"
|
||||||
|
|
||||||
#: ../vnc.html:141
|
#: ../vnc.html:135
|
||||||
msgid "Clear"
|
msgid "Edit clipboard content in the textarea below."
|
||||||
msgstr "Rensa"
|
msgstr "Redigera urklippets innehåll i fältet nedan."
|
||||||
|
|
||||||
#: ../vnc.html:147
|
#: ../vnc.html:143
|
||||||
msgid "Fullscreen"
|
msgid "Full Screen"
|
||||||
msgstr "Fullskärm"
|
msgstr "Fullskärm"
|
||||||
|
|
||||||
#: ../vnc.html:152 ../vnc.html:159
|
#: ../vnc.html:148 ../vnc.html:154
|
||||||
msgid "Settings"
|
msgid "Settings"
|
||||||
msgstr "Inställningar"
|
msgstr "Inställningar"
|
||||||
|
|
||||||
#: ../vnc.html:162
|
#: ../vnc.html:158
|
||||||
msgid "Shared Mode"
|
msgid "Shared Mode"
|
||||||
msgstr "Delat Läge"
|
msgstr "Delat Läge"
|
||||||
|
|
||||||
#: ../vnc.html:165
|
#: ../vnc.html:161
|
||||||
msgid "View Only"
|
msgid "View Only"
|
||||||
msgstr "Endast Visning"
|
msgstr "Endast Visning"
|
||||||
|
|
||||||
#: ../vnc.html:169
|
#: ../vnc.html:165
|
||||||
msgid "Clip to Window"
|
msgid "Clip to Window"
|
||||||
msgstr "Begränsa till Fönster"
|
msgstr "Begränsa till Fönster"
|
||||||
|
|
||||||
#: ../vnc.html:172
|
#: ../vnc.html:168
|
||||||
msgid "Scaling Mode:"
|
msgid "Scaling Mode:"
|
||||||
msgstr "Skalningsläge:"
|
msgstr "Skalningsläge:"
|
||||||
|
|
||||||
#: ../vnc.html:174
|
#: ../vnc.html:170
|
||||||
msgid "None"
|
msgid "None"
|
||||||
msgstr "Ingen"
|
msgstr "Ingen"
|
||||||
|
|
||||||
#: ../vnc.html:175
|
#: ../vnc.html:171
|
||||||
msgid "Local Scaling"
|
msgid "Local Scaling"
|
||||||
msgstr "Lokal Skalning"
|
msgstr "Lokal Skalning"
|
||||||
|
|
||||||
#: ../vnc.html:176
|
#: ../vnc.html:172
|
||||||
msgid "Remote Resizing"
|
msgid "Remote Resizing"
|
||||||
msgstr "Ändra Storlek"
|
msgstr "Ändra Storlek"
|
||||||
|
|
||||||
#: ../vnc.html:181
|
#: ../vnc.html:177
|
||||||
msgid "Advanced"
|
msgid "Advanced"
|
||||||
msgstr "Avancerat"
|
msgstr "Avancerat"
|
||||||
|
|
||||||
#: ../vnc.html:184
|
#: ../vnc.html:180
|
||||||
msgid "Quality:"
|
msgid "Quality:"
|
||||||
msgstr "Kvalitet:"
|
msgstr "Kvalitet:"
|
||||||
|
|
||||||
#: ../vnc.html:188
|
#: ../vnc.html:184
|
||||||
msgid "Compression level:"
|
msgid "Compression level:"
|
||||||
msgstr "Kompressionsnivå:"
|
msgstr "Kompressionsnivå:"
|
||||||
|
|
||||||
#: ../vnc.html:193
|
#: ../vnc.html:189
|
||||||
msgid "Repeater ID:"
|
msgid "Repeater ID:"
|
||||||
msgstr "Repeater-ID:"
|
msgstr "Repeater-ID:"
|
||||||
|
|
||||||
#: ../vnc.html:197
|
#: ../vnc.html:193
|
||||||
msgid "WebSocket"
|
msgid "WebSocket"
|
||||||
msgstr "WebSocket"
|
msgstr "WebSocket"
|
||||||
|
|
||||||
#: ../vnc.html:200
|
#: ../vnc.html:196
|
||||||
msgid "Encrypt"
|
msgid "Encrypt"
|
||||||
msgstr "Kryptera"
|
msgstr "Kryptera"
|
||||||
|
|
||||||
#: ../vnc.html:203
|
#: ../vnc.html:199
|
||||||
msgid "Host:"
|
msgid "Host:"
|
||||||
msgstr "Värd:"
|
msgstr "Värd:"
|
||||||
|
|
||||||
#: ../vnc.html:207
|
#: ../vnc.html:203
|
||||||
msgid "Port:"
|
msgid "Port:"
|
||||||
msgstr "Port:"
|
msgstr "Port:"
|
||||||
|
|
||||||
#: ../vnc.html:211
|
#: ../vnc.html:207
|
||||||
msgid "Path:"
|
msgid "Path:"
|
||||||
msgstr "Sökväg:"
|
msgstr "Sökväg:"
|
||||||
|
|
||||||
#: ../vnc.html:218
|
#: ../vnc.html:214
|
||||||
msgid "Automatic Reconnect"
|
msgid "Automatic Reconnect"
|
||||||
msgstr "Automatisk Återanslutning"
|
msgstr "Automatisk Återanslutning"
|
||||||
|
|
||||||
#: ../vnc.html:221
|
#: ../vnc.html:217
|
||||||
msgid "Reconnect Delay (ms):"
|
msgid "Reconnect Delay (ms):"
|
||||||
msgstr "Fördröjning (ms):"
|
msgstr "Fördröjning (ms):"
|
||||||
|
|
||||||
#: ../vnc.html:226
|
#: ../vnc.html:222
|
||||||
msgid "Show Dot when No Cursor"
|
msgid "Show Dot when No Cursor"
|
||||||
msgstr "Visa prick när ingen muspekare finns"
|
msgstr "Visa prick när ingen muspekare finns"
|
||||||
|
|
||||||
#: ../vnc.html:231
|
#: ../vnc.html:227
|
||||||
msgid "Logging:"
|
msgid "Logging:"
|
||||||
msgstr "Loggning:"
|
msgstr "Loggning:"
|
||||||
|
|
||||||
#: ../vnc.html:240
|
#: ../vnc.html:236
|
||||||
msgid "Version:"
|
msgid "Version:"
|
||||||
msgstr "Version:"
|
msgstr "Version:"
|
||||||
|
|
||||||
#: ../vnc.html:248
|
#: ../vnc.html:244
|
||||||
msgid "Disconnect"
|
msgid "Disconnect"
|
||||||
msgstr "Koppla från"
|
msgstr "Koppla från"
|
||||||
|
|
||||||
|
|
@ -283,18 +287,53 @@ msgstr "Koppla från"
|
||||||
msgid "Connect"
|
msgid "Connect"
|
||||||
msgstr "Anslut"
|
msgstr "Anslut"
|
||||||
|
|
||||||
#: ../vnc.html:277
|
#: ../vnc.html:276
|
||||||
|
msgid "Server identity"
|
||||||
|
msgstr "Server-identitet"
|
||||||
|
|
||||||
|
#: ../vnc.html:279
|
||||||
|
msgid "The server has provided the following identifying information:"
|
||||||
|
msgstr "Servern har gett följande identifierande information:"
|
||||||
|
|
||||||
|
#: ../vnc.html:283
|
||||||
|
msgid "Fingerprint:"
|
||||||
|
msgstr "Fingeravtryck:"
|
||||||
|
|
||||||
|
#: ../vnc.html:286
|
||||||
|
msgid ""
|
||||||
|
"Please verify that the information is correct and press \"Approve\". "
|
||||||
|
"Otherwise press \"Reject\"."
|
||||||
|
msgstr ""
|
||||||
|
"Kontrollera att informationen är korrekt och tryck sedan "
|
||||||
|
"\"Godkänn\". Tryck annars \"Neka\"."
|
||||||
|
|
||||||
|
#: ../vnc.html:291
|
||||||
|
msgid "Approve"
|
||||||
|
msgstr "Godkänn"
|
||||||
|
|
||||||
|
#: ../vnc.html:292
|
||||||
|
msgid "Reject"
|
||||||
|
msgstr "Neka"
|
||||||
|
|
||||||
|
#: ../vnc.html:300
|
||||||
|
msgid "Credentials"
|
||||||
|
msgstr "Användaruppgifter"
|
||||||
|
|
||||||
|
#: ../vnc.html:304
|
||||||
msgid "Username:"
|
msgid "Username:"
|
||||||
msgstr "Användarnamn:"
|
msgstr "Användarnamn:"
|
||||||
|
|
||||||
#: ../vnc.html:281
|
#: ../vnc.html:308
|
||||||
msgid "Password:"
|
msgid "Password:"
|
||||||
msgstr "Lösenord:"
|
msgstr "Lösenord:"
|
||||||
|
|
||||||
#: ../vnc.html:285
|
#: ../vnc.html:312
|
||||||
msgid "Send Credentials"
|
msgid "Send Credentials"
|
||||||
msgstr "Skicka Användaruppgifter"
|
msgstr "Skicka Användaruppgifter"
|
||||||
|
|
||||||
#: ../vnc.html:295
|
#: ../vnc.html:321
|
||||||
msgid "Cancel"
|
msgid "Cancel"
|
||||||
msgstr "Avbryt"
|
msgstr "Avbryt"
|
||||||
|
|
||||||
|
#~ msgid "Clear"
|
||||||
|
#~ msgstr "Rensa"
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ parts:
|
||||||
- jq
|
- jq
|
||||||
|
|
||||||
websockify:
|
websockify:
|
||||||
source: https://github.com/novnc/websockify/archive/v0.9.0.tar.gz
|
source: https://github.com/novnc/websockify/archive/v0.11.0.tar.gz
|
||||||
plugin: python
|
plugin: python
|
||||||
stage-packages:
|
stage-packages:
|
||||||
- python3-numpy
|
- python3-numpy
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ export default class FakeWebSocket {
|
||||||
}
|
}
|
||||||
|
|
||||||
_getSentData() {
|
_getSentData() {
|
||||||
const res = new Uint8Array(this._sendQueue.buffer, 0, this.bufferedAmount);
|
const res = this._sendQueue.slice(0, this.bufferedAmount);
|
||||||
this.bufferedAmount = 0;
|
this.bufferedAmount = 0;
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
@ -55,11 +55,15 @@ export default class FakeWebSocket {
|
||||||
}
|
}
|
||||||
|
|
||||||
_receiveData(data) {
|
_receiveData(data) {
|
||||||
// Break apart the data to expose bugs where we assume data is
|
if (data.length < 4096) {
|
||||||
// neatly packaged
|
// Break apart the data to expose bugs where we assume data is
|
||||||
for (let i = 0;i < data.length;i++) {
|
// neatly packaged
|
||||||
let buf = data.subarray(i, i+1);
|
for (let i = 0;i < data.length;i++) {
|
||||||
this.onmessage(new MessageEvent("message", { 'data': buf }));
|
let buf = data.slice(i, i+1);
|
||||||
|
this.onmessage(new MessageEvent("message", { 'data': buf.buffer }));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.onmessage(new MessageEvent("message", { 'data': data.buffer }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ function loadFile() {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const script = document.createElement("script");
|
const script = document.createElement("script");
|
||||||
script.onload = resolve;
|
script.onload = resolve;
|
||||||
script.onerror = reject;
|
script.onerror = () => { reject("Failed to load " + fname); };
|
||||||
document.body.appendChild(script);
|
document.body.appendChild(script);
|
||||||
script.src = "../recordings/" + fname;
|
script.src = "../recordings/" + fname;
|
||||||
});
|
});
|
||||||
|
|
@ -200,6 +200,9 @@ function start() {
|
||||||
player.onrfbdisconnected = (evt) => {
|
player.onrfbdisconnected = (evt) => {
|
||||||
if (!evt.detail.clean) {
|
if (!evt.detail.clean) {
|
||||||
message(`noVNC sent disconnected during iteration ${evt.detail.iteration} frame ${evt.detail.frame}`);
|
message(`noVNC sent disconnected during iteration ${evt.detail.iteration} frame ${evt.detail.frame}`);
|
||||||
|
|
||||||
|
document.getElementById('startButton').disabled = false;
|
||||||
|
document.getElementById('startButton').value = "Start";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
player.onfinish = (evt) => {
|
player.onfinish = (evt) => {
|
||||||
|
|
|
||||||
|
|
@ -131,12 +131,10 @@ export default class RecordingPlayer {
|
||||||
_doPacket() {
|
_doPacket() {
|
||||||
// Avoid having excessive queue buildup in non-realtime mode
|
// Avoid having excessive queue buildup in non-realtime mode
|
||||||
if (this._trafficManagement && this._rfb._flushing) {
|
if (this._trafficManagement && this._rfb._flushing) {
|
||||||
const orig = this._rfb._display.onflush;
|
this._rfb.flush()
|
||||||
this._rfb._display.onflush = () => {
|
.then(() => {
|
||||||
this._rfb._display.onflush = orig;
|
this._doPacket();
|
||||||
this._rfb._onFlush();
|
});
|
||||||
this._doPacket();
|
|
||||||
};
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -150,13 +148,8 @@ export default class RecordingPlayer {
|
||||||
|
|
||||||
_finish() {
|
_finish() {
|
||||||
if (this._rfb._display.pending()) {
|
if (this._rfb._display.pending()) {
|
||||||
this._rfb._display.onflush = () => {
|
this._rfb._display.flush()
|
||||||
if (this._rfb._flushing) {
|
.then(() => { this._finish(); });
|
||||||
this._rfb._onFlush();
|
|
||||||
}
|
|
||||||
this._finish();
|
|
||||||
};
|
|
||||||
this._rfb._display.flush();
|
|
||||||
} else {
|
} else {
|
||||||
this._running = false;
|
this._running = false;
|
||||||
this._ws.onclose({code: 1000, reason: ""});
|
this._ws.onclose({code: 1000, reason: ""});
|
||||||
|
|
|
||||||
|
|
@ -9,23 +9,26 @@ import FakeWebSocket from './fake.websocket.js';
|
||||||
|
|
||||||
function testDecodeRect(decoder, x, y, width, height, data, display, depth) {
|
function testDecodeRect(decoder, x, y, width, height, data, display, depth) {
|
||||||
let sock;
|
let sock;
|
||||||
|
let done = false;
|
||||||
|
|
||||||
sock = new Websock;
|
sock = new Websock;
|
||||||
sock.open("ws://example.com");
|
sock.open("ws://example.com");
|
||||||
|
|
||||||
sock.on('message', () => {
|
sock.on('message', () => {
|
||||||
decoder.decodeRect(x, y, width, height, sock, display, depth);
|
done = decoder.decodeRect(x, y, width, height, sock, display, depth);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Empty messages are filtered at multiple layers, so we need to
|
// Empty messages are filtered at multiple layers, so we need to
|
||||||
// do a direct call
|
// do a direct call
|
||||||
if (data.length === 0) {
|
if (data.length === 0) {
|
||||||
decoder.decodeRect(x, y, width, height, sock, display, depth);
|
done = decoder.decodeRect(x, y, width, height, sock, display, depth);
|
||||||
} else {
|
} else {
|
||||||
sock._websocket._receiveData(new Uint8Array(data));
|
sock._websocket._receiveData(new Uint8Array(data));
|
||||||
}
|
}
|
||||||
|
|
||||||
display.flip();
|
display.flip();
|
||||||
|
|
||||||
|
return done;
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('CopyRect Decoder', function () {
|
describe('CopyRect Decoder', function () {
|
||||||
|
|
@ -47,12 +50,15 @@ describe('CopyRect Decoder', function () {
|
||||||
display.fillRect(0, 0, 2, 2, [ 0x00, 0x00, 0xff ]);
|
display.fillRect(0, 0, 2, 2, [ 0x00, 0x00, 0xff ]);
|
||||||
display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]);
|
display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]);
|
||||||
|
|
||||||
testDecodeRect(decoder, 0, 2, 2, 2,
|
let done;
|
||||||
[0x00, 0x02, 0x00, 0x00],
|
done = testDecodeRect(decoder, 0, 2, 2, 2,
|
||||||
display, 24);
|
[0x00, 0x02, 0x00, 0x00],
|
||||||
testDecodeRect(decoder, 2, 2, 2, 2,
|
display, 24);
|
||||||
[0x00, 0x00, 0x00, 0x00],
|
expect(done).to.be.true;
|
||||||
display, 24);
|
done = testDecodeRect(decoder, 2, 2, 2, 2,
|
||||||
|
[0x00, 0x00, 0x00, 0x00],
|
||||||
|
display, 24);
|
||||||
|
expect(done).to.be.true;
|
||||||
|
|
||||||
let targetData = new Uint8Array([
|
let targetData = new Uint8Array([
|
||||||
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
|
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
|
||||||
|
|
@ -69,7 +75,9 @@ describe('CopyRect Decoder', function () {
|
||||||
display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]);
|
display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]);
|
||||||
display.fillRect(0, 2, 2, 2, [ 0x00, 0xff, 0x00 ]);
|
display.fillRect(0, 2, 2, 2, [ 0x00, 0xff, 0x00 ]);
|
||||||
|
|
||||||
testDecodeRect(decoder, 1, 2, 0, 0, [0x00, 0x00, 0x00, 0x00], display, 24);
|
let done = testDecodeRect(decoder, 1, 2, 0, 0,
|
||||||
|
[0x00, 0x00, 0x00, 0x00],
|
||||||
|
display, 24);
|
||||||
|
|
||||||
let targetData = new Uint8Array([
|
let targetData = new Uint8Array([
|
||||||
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
|
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
|
||||||
|
|
@ -78,6 +86,7 @@ describe('CopyRect Decoder', function () {
|
||||||
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
|
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
expect(done).to.be.true;
|
||||||
expect(display).to.have.displayed(targetData);
|
expect(display).to.have.displayed(targetData);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -298,14 +298,11 @@ describe('Display/Canvas Helper', function () {
|
||||||
expect(display).to.have.displayed(checkedData);
|
expect(display).to.have.displayed(checkedData);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should support drawing images via #imageRect', function (done) {
|
it('should support drawing images via #imageRect', async function () {
|
||||||
display.imageRect(0, 0, 4, 4, "image/png", makeImagePng(checkedData, 4, 4));
|
display.imageRect(0, 0, 4, 4, "image/png", makeImagePng(checkedData, 4, 4));
|
||||||
display.flip();
|
display.flip();
|
||||||
display.onflush = () => {
|
await display.flush();
|
||||||
expect(display).to.have.displayed(checkedData);
|
expect(display).to.have.displayed(checkedData);
|
||||||
done();
|
|
||||||
};
|
|
||||||
display.flush();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should support blit images with true color via #blitImage', function () {
|
it('should support blit images with true color via #blitImage', function () {
|
||||||
|
|
@ -360,12 +357,11 @@ describe('Display/Canvas Helper', function () {
|
||||||
expect(img.addEventListener).to.have.been.calledOnce;
|
expect(img.addEventListener).to.have.been.calledOnce;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should call callback when queue is flushed', function () {
|
it('should resolve promise when queue is flushed', async function () {
|
||||||
display.onflush = sinon.spy();
|
|
||||||
display.fillRect(0, 0, 4, 4, [0, 0xff, 0]);
|
display.fillRect(0, 0, 4, 4, [0, 0xff, 0]);
|
||||||
expect(display.onflush).to.not.have.been.called;
|
let promise = display.flush();
|
||||||
display.flush();
|
expect(promise).to.be.an.instanceOf(Promise);
|
||||||
expect(display.onflush).to.have.been.calledOnce;
|
await promise;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should draw a blit image on type "blit"', function () {
|
it('should draw a blit image on type "blit"', function () {
|
||||||
|
|
|
||||||
|
|
@ -9,23 +9,26 @@ import FakeWebSocket from './fake.websocket.js';
|
||||||
|
|
||||||
function testDecodeRect(decoder, x, y, width, height, data, display, depth) {
|
function testDecodeRect(decoder, x, y, width, height, data, display, depth) {
|
||||||
let sock;
|
let sock;
|
||||||
|
let done = false;
|
||||||
|
|
||||||
sock = new Websock;
|
sock = new Websock;
|
||||||
sock.open("ws://example.com");
|
sock.open("ws://example.com");
|
||||||
|
|
||||||
sock.on('message', () => {
|
sock.on('message', () => {
|
||||||
decoder.decodeRect(x, y, width, height, sock, display, depth);
|
done = decoder.decodeRect(x, y, width, height, sock, display, depth);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Empty messages are filtered at multiple layers, so we need to
|
// Empty messages are filtered at multiple layers, so we need to
|
||||||
// do a direct call
|
// do a direct call
|
||||||
if (data.length === 0) {
|
if (data.length === 0) {
|
||||||
decoder.decodeRect(x, y, width, height, sock, display, depth);
|
done = decoder.decodeRect(x, y, width, height, sock, display, depth);
|
||||||
} else {
|
} else {
|
||||||
sock._websocket._receiveData(new Uint8Array(data));
|
sock._websocket._receiveData(new Uint8Array(data));
|
||||||
}
|
}
|
||||||
|
|
||||||
display.flip();
|
display.flip();
|
||||||
|
|
||||||
|
return done;
|
||||||
}
|
}
|
||||||
|
|
||||||
function push32(arr, num) {
|
function push32(arr, num) {
|
||||||
|
|
@ -62,7 +65,7 @@ describe('Hextile Decoder', function () {
|
||||||
data.push(2 | (2 << 4)); // x: 2, y: 2
|
data.push(2 | (2 << 4)); // x: 2, y: 2
|
||||||
data.push(1 | (1 << 4)); // width: 2, height: 2
|
data.push(1 | (1 << 4)); // width: 2, height: 2
|
||||||
|
|
||||||
testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
|
let done = testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
|
||||||
|
|
||||||
let targetData = new Uint8Array([
|
let targetData = new Uint8Array([
|
||||||
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
|
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
|
||||||
|
|
@ -71,6 +74,7 @@ describe('Hextile Decoder', function () {
|
||||||
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
|
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
expect(done).to.be.true;
|
||||||
expect(display).to.have.displayed(targetData);
|
expect(display).to.have.displayed(targetData);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -92,8 +96,9 @@ describe('Hextile Decoder', function () {
|
||||||
data.push(0);
|
data.push(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
|
let done = testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
|
||||||
|
|
||||||
|
expect(done).to.be.true;
|
||||||
expect(display).to.have.displayed(targetData);
|
expect(display).to.have.displayed(targetData);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -102,13 +107,14 @@ describe('Hextile Decoder', function () {
|
||||||
data.push(0x02);
|
data.push(0x02);
|
||||||
push32(data, 0x00ff0000); // becomes 00ff0000 --> #00FF00 bg color
|
push32(data, 0x00ff0000); // becomes 00ff0000 --> #00FF00 bg color
|
||||||
|
|
||||||
testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
|
let done = testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
|
||||||
|
|
||||||
let expected = [];
|
let expected = [];
|
||||||
for (let i = 0; i < 16; i++) {
|
for (let i = 0; i < 16; i++) {
|
||||||
push32(expected, 0x00ff00ff);
|
push32(expected, 0x00ff00ff);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
expect(done).to.be.true;
|
||||||
expect(display).to.have.displayed(new Uint8Array(expected));
|
expect(display).to.have.displayed(new Uint8Array(expected));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -125,7 +131,7 @@ describe('Hextile Decoder', function () {
|
||||||
// send an empty frame
|
// send an empty frame
|
||||||
data.push(0x00);
|
data.push(0x00);
|
||||||
|
|
||||||
testDecodeRect(decoder, 0, 0, 32, 4, data, display, 24);
|
let done = testDecodeRect(decoder, 0, 0, 32, 4, data, display, 24);
|
||||||
|
|
||||||
let expected = [];
|
let expected = [];
|
||||||
for (let i = 0; i < 16; i++) {
|
for (let i = 0; i < 16; i++) {
|
||||||
|
|
@ -135,6 +141,7 @@ describe('Hextile Decoder', function () {
|
||||||
push32(expected, 0x00ff00ff); // rect 2: same bkground color
|
push32(expected, 0x00ff00ff); // rect 2: same bkground color
|
||||||
}
|
}
|
||||||
|
|
||||||
|
expect(done).to.be.true;
|
||||||
expect(display).to.have.displayed(new Uint8Array(expected));
|
expect(display).to.have.displayed(new Uint8Array(expected));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -156,7 +163,7 @@ describe('Hextile Decoder', function () {
|
||||||
data.push(2 | (2 << 4)); // x: 2, y: 2
|
data.push(2 | (2 << 4)); // x: 2, y: 2
|
||||||
data.push(1 | (1 << 4)); // width: 2, height: 2
|
data.push(1 | (1 << 4)); // width: 2, height: 2
|
||||||
|
|
||||||
testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
|
let done = testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
|
||||||
|
|
||||||
let targetData = new Uint8Array([
|
let targetData = new Uint8Array([
|
||||||
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
|
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
|
||||||
|
|
@ -165,6 +172,7 @@ describe('Hextile Decoder', function () {
|
||||||
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
|
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
expect(done).to.be.true;
|
||||||
expect(display).to.have.displayed(targetData);
|
expect(display).to.have.displayed(targetData);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -190,7 +198,7 @@ describe('Hextile Decoder', function () {
|
||||||
data.push(0); // x: 0, y: 0
|
data.push(0); // x: 0, y: 0
|
||||||
data.push(1 | (1 << 4)); // width: 2, height: 2
|
data.push(1 | (1 << 4)); // width: 2, height: 2
|
||||||
|
|
||||||
testDecodeRect(decoder, 0, 0, 4, 17, data, display, 24);
|
let done = testDecodeRect(decoder, 0, 0, 4, 17, data, display, 24);
|
||||||
|
|
||||||
let targetData = [
|
let targetData = [
|
||||||
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
|
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
|
||||||
|
|
@ -205,6 +213,7 @@ describe('Hextile Decoder', function () {
|
||||||
}
|
}
|
||||||
expected = expected.concat(targetData.slice(0, 16));
|
expected = expected.concat(targetData.slice(0, 16));
|
||||||
|
|
||||||
|
expect(done).to.be.true;
|
||||||
expect(display).to.have.displayed(new Uint8Array(expected));
|
expect(display).to.have.displayed(new Uint8Array(expected));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -218,7 +227,7 @@ describe('Hextile Decoder', function () {
|
||||||
display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]);
|
display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]);
|
||||||
display.fillRect(0, 2, 2, 2, [ 0x00, 0xff, 0x00 ]);
|
display.fillRect(0, 2, 2, 2, [ 0x00, 0xff, 0x00 ]);
|
||||||
|
|
||||||
testDecodeRect(decoder, 1, 2, 0, 0, [], display, 24);
|
let done = testDecodeRect(decoder, 1, 2, 0, 0, [], display, 24);
|
||||||
|
|
||||||
let targetData = new Uint8Array([
|
let targetData = new Uint8Array([
|
||||||
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
|
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
|
||||||
|
|
@ -227,6 +236,7 @@ describe('Hextile Decoder', function () {
|
||||||
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
|
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
expect(done).to.be.true;
|
||||||
expect(display).to.have.displayed(targetData);
|
expect(display).to.have.displayed(targetData);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -9,23 +9,26 @@ import FakeWebSocket from './fake.websocket.js';
|
||||||
|
|
||||||
function testDecodeRect(decoder, x, y, width, height, data, display, depth) {
|
function testDecodeRect(decoder, x, y, width, height, data, display, depth) {
|
||||||
let sock;
|
let sock;
|
||||||
|
let done = false;
|
||||||
|
|
||||||
sock = new Websock;
|
sock = new Websock;
|
||||||
sock.open("ws://example.com");
|
sock.open("ws://example.com");
|
||||||
|
|
||||||
sock.on('message', () => {
|
sock.on('message', () => {
|
||||||
decoder.decodeRect(x, y, width, height, sock, display, depth);
|
done = decoder.decodeRect(x, y, width, height, sock, display, depth);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Empty messages are filtered at multiple layers, so we need to
|
// Empty messages are filtered at multiple layers, so we need to
|
||||||
// do a direct call
|
// do a direct call
|
||||||
if (data.length === 0) {
|
if (data.length === 0) {
|
||||||
decoder.decodeRect(x, y, width, height, sock, display, depth);
|
done = decoder.decodeRect(x, y, width, height, sock, display, depth);
|
||||||
} else {
|
} else {
|
||||||
sock._websocket._receiveData(new Uint8Array(data));
|
sock._websocket._receiveData(new Uint8Array(data));
|
||||||
}
|
}
|
||||||
|
|
||||||
display.flip();
|
display.flip();
|
||||||
|
|
||||||
|
return done;
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('JPEG Decoder', function () {
|
describe('JPEG Decoder', function () {
|
||||||
|
|
@ -41,7 +44,7 @@ describe('JPEG Decoder', function () {
|
||||||
display.resize(4, 4);
|
display.resize(4, 4);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle JPEG rects', function (done) {
|
it('should handle JPEG rects', async function () {
|
||||||
let data = [
|
let data = [
|
||||||
// JPEG data
|
// JPEG data
|
||||||
0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46,
|
0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46,
|
||||||
|
|
@ -131,7 +134,8 @@ describe('JPEG Decoder', function () {
|
||||||
0xff, 0xd9,
|
0xff, 0xd9,
|
||||||
];
|
];
|
||||||
|
|
||||||
testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
|
let decodeDone = testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
|
||||||
|
expect(decodeDone).to.be.true;
|
||||||
|
|
||||||
let targetData = new Uint8Array([
|
let targetData = new Uint8Array([
|
||||||
0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255,
|
0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255,
|
||||||
|
|
@ -147,14 +151,11 @@ describe('JPEG Decoder', function () {
|
||||||
return diff < 5;
|
return diff < 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
display.onflush = () => {
|
await display.flush();
|
||||||
expect(display).to.have.displayed(targetData, almost);
|
expect(display).to.have.displayed(targetData, almost);
|
||||||
done();
|
|
||||||
};
|
|
||||||
display.flush();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle JPEG rects without Huffman and quantification tables', function (done) {
|
it('should handle JPEG rects without Huffman and quantification tables', async function () {
|
||||||
let data1 = [
|
let data1 = [
|
||||||
// JPEG data
|
// JPEG data
|
||||||
0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46,
|
0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46,
|
||||||
|
|
@ -244,7 +245,12 @@ describe('JPEG Decoder', function () {
|
||||||
0xff, 0xd9,
|
0xff, 0xd9,
|
||||||
];
|
];
|
||||||
|
|
||||||
testDecodeRect(decoder, 0, 0, 4, 4, data1, display, 24);
|
let decodeDone;
|
||||||
|
|
||||||
|
decodeDone = testDecodeRect(decoder, 0, 0, 4, 4, data1, display, 24);
|
||||||
|
expect(decodeDone).to.be.true;
|
||||||
|
|
||||||
|
display.fillRect(0, 0, 4, 4, [128, 128, 128, 255]);
|
||||||
|
|
||||||
let data2 = [
|
let data2 = [
|
||||||
// JPEG data
|
// JPEG data
|
||||||
|
|
@ -263,7 +269,8 @@ describe('JPEG Decoder', function () {
|
||||||
0xcf, 0xff, 0x00, 0x0b, 0xab, 0x1f, 0xff, 0xd9,
|
0xcf, 0xff, 0x00, 0x0b, 0xab, 0x1f, 0xff, 0xd9,
|
||||||
];
|
];
|
||||||
|
|
||||||
testDecodeRect(decoder, 0, 0, 4, 4, data2, display, 24);
|
decodeDone = testDecodeRect(decoder, 0, 0, 4, 4, data2, display, 24);
|
||||||
|
expect(decodeDone).to.be.true;
|
||||||
|
|
||||||
let targetData = new Uint8Array([
|
let targetData = new Uint8Array([
|
||||||
0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255,
|
0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255,
|
||||||
|
|
@ -279,10 +286,7 @@ describe('JPEG Decoder', function () {
|
||||||
return diff < 5;
|
return diff < 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
display.onflush = () => {
|
await display.flush();
|
||||||
expect(display).to.have.displayed(targetData, almost);
|
expect(display).to.have.displayed(targetData, almost);
|
||||||
done();
|
|
||||||
};
|
|
||||||
display.flush();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,61 +1,146 @@
|
||||||
const expect = chai.expect;
|
const expect = chai.expect;
|
||||||
import { l10n } from '../app/localization.js';
|
import _, { Localizer, l10n } from '../app/localization.js';
|
||||||
|
|
||||||
describe('Localization', function () {
|
describe('Localization', function () {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
|
let origNavigator;
|
||||||
|
let fetch;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
// window.navigator is a protected read-only property in many
|
||||||
|
// environments, so we need to redefine it whilst running these
|
||||||
|
// tests.
|
||||||
|
origNavigator = Object.getOwnPropertyDescriptor(window, "navigator");
|
||||||
|
|
||||||
|
Object.defineProperty(window, "navigator", {value: {}});
|
||||||
|
window.navigator.languages = [];
|
||||||
|
|
||||||
|
fetch = sinon.stub(window, "fetch");
|
||||||
|
fetch.resolves(new Response("{}"));
|
||||||
|
});
|
||||||
|
afterEach(function () {
|
||||||
|
fetch.restore();
|
||||||
|
|
||||||
|
Object.defineProperty(window, "navigator", origNavigator);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Singleton', function () {
|
||||||
|
it('should export a singleton object', function () {
|
||||||
|
expect(l10n).to.be.instanceOf(Localizer);
|
||||||
|
});
|
||||||
|
it('should export a singleton translation function', async function () {
|
||||||
|
// FIXME: Can we use some spy instead?
|
||||||
|
window.navigator.languages = ["de"];
|
||||||
|
fetch.resolves(new Response(JSON.stringify({ "Foobar": "gazonk" })));
|
||||||
|
await l10n.setup(["de"]);
|
||||||
|
expect(_("Foobar")).to.equal("gazonk");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('language selection', function () {
|
describe('language selection', function () {
|
||||||
let origNavigator;
|
|
||||||
beforeEach(function () {
|
|
||||||
// window.navigator is a protected read-only property in many
|
|
||||||
// environments, so we need to redefine it whilst running these
|
|
||||||
// tests.
|
|
||||||
origNavigator = Object.getOwnPropertyDescriptor(window, "navigator");
|
|
||||||
|
|
||||||
Object.defineProperty(window, "navigator", {value: {}});
|
|
||||||
window.navigator.languages = [];
|
|
||||||
});
|
|
||||||
afterEach(function () {
|
|
||||||
Object.defineProperty(window, "navigator", origNavigator);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should use English by default', function () {
|
it('should use English by default', function () {
|
||||||
expect(l10n.language).to.equal('en');
|
let lclz = new Localizer();
|
||||||
|
expect(lclz.language).to.equal('en');
|
||||||
});
|
});
|
||||||
it('should use English if no user language matches', function () {
|
it('should use English if no user language matches', async function () {
|
||||||
window.navigator.languages = ["nl", "de"];
|
window.navigator.languages = ["nl", "de"];
|
||||||
l10n.setup(["es", "fr"]);
|
let lclz = new Localizer();
|
||||||
expect(l10n.language).to.equal('en');
|
await lclz.setup(["es", "fr"]);
|
||||||
|
expect(lclz.language).to.equal('en');
|
||||||
});
|
});
|
||||||
it('should use the most preferred user language', function () {
|
it('should fall back to generic English for other English', async function () {
|
||||||
|
window.navigator.languages = ["en-AU", "de"];
|
||||||
|
let lclz = new Localizer();
|
||||||
|
await lclz.setup(["de", "fr", "en-GB"]);
|
||||||
|
expect(lclz.language).to.equal('en');
|
||||||
|
});
|
||||||
|
it('should prefer specific English over generic', async function () {
|
||||||
|
window.navigator.languages = ["en-GB", "de"];
|
||||||
|
let lclz = new Localizer();
|
||||||
|
await lclz.setup(["de", "en-AU", "en-GB"]);
|
||||||
|
expect(lclz.language).to.equal('en-GB');
|
||||||
|
});
|
||||||
|
it('should use the most preferred user language', async function () {
|
||||||
window.navigator.languages = ["nl", "de", "fr"];
|
window.navigator.languages = ["nl", "de", "fr"];
|
||||||
l10n.setup(["es", "fr", "de"]);
|
let lclz = new Localizer();
|
||||||
expect(l10n.language).to.equal('de');
|
await lclz.setup(["es", "fr", "de"]);
|
||||||
|
expect(lclz.language).to.equal('de');
|
||||||
});
|
});
|
||||||
it('should prefer sub-languages languages', function () {
|
it('should prefer sub-languages languages', async function () {
|
||||||
window.navigator.languages = ["pt-BR"];
|
window.navigator.languages = ["pt-BR"];
|
||||||
l10n.setup(["pt", "pt-BR"]);
|
let lclz = new Localizer();
|
||||||
expect(l10n.language).to.equal('pt-BR');
|
await lclz.setup(["pt", "pt-BR"]);
|
||||||
|
expect(lclz.language).to.equal('pt-BR');
|
||||||
});
|
});
|
||||||
it('should fall back to language "parents"', function () {
|
it('should fall back to language "parents"', async function () {
|
||||||
window.navigator.languages = ["pt-BR"];
|
window.navigator.languages = ["pt-BR"];
|
||||||
l10n.setup(["fr", "pt", "de"]);
|
let lclz = new Localizer();
|
||||||
expect(l10n.language).to.equal('pt');
|
await lclz.setup(["fr", "pt", "de"]);
|
||||||
|
expect(lclz.language).to.equal('pt');
|
||||||
});
|
});
|
||||||
it('should not use specific language when user asks for a generic language', function () {
|
it('should not use specific language when user asks for a generic language', async function () {
|
||||||
window.navigator.languages = ["pt", "de"];
|
window.navigator.languages = ["pt", "de"];
|
||||||
l10n.setup(["fr", "pt-BR", "de"]);
|
let lclz = new Localizer();
|
||||||
expect(l10n.language).to.equal('de');
|
await lclz.setup(["fr", "pt-BR", "de"]);
|
||||||
|
expect(lclz.language).to.equal('de');
|
||||||
});
|
});
|
||||||
it('should handle underscore as a separator', function () {
|
it('should handle underscore as a separator', async function () {
|
||||||
window.navigator.languages = ["pt-BR"];
|
window.navigator.languages = ["pt-BR"];
|
||||||
l10n.setup(["pt_BR"]);
|
let lclz = new Localizer();
|
||||||
expect(l10n.language).to.equal('pt_BR');
|
await lclz.setup(["pt_BR"]);
|
||||||
|
expect(lclz.language).to.equal('pt_BR');
|
||||||
});
|
});
|
||||||
it('should handle difference in case', function () {
|
it('should handle difference in case', async function () {
|
||||||
window.navigator.languages = ["pt-br"];
|
window.navigator.languages = ["pt-br"];
|
||||||
l10n.setup(["pt-BR"]);
|
let lclz = new Localizer();
|
||||||
expect(l10n.language).to.equal('pt-BR');
|
await lclz.setup(["pt-BR"]);
|
||||||
|
expect(lclz.language).to.equal('pt-BR');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Translation loading', function () {
|
||||||
|
it('should not fetch a translation for English', async function () {
|
||||||
|
window.navigator.languages = [];
|
||||||
|
let lclz = new Localizer();
|
||||||
|
await lclz.setup([]);
|
||||||
|
expect(fetch).to.not.have.been.called;
|
||||||
|
});
|
||||||
|
it('should fetch dictionary relative base URL', async function () {
|
||||||
|
window.navigator.languages = ["de", "fr"];
|
||||||
|
fetch.resolves(new Response('{ "Foobar": "gazonk" }'));
|
||||||
|
let lclz = new Localizer();
|
||||||
|
await lclz.setup(["ru", "fr"], "/some/path/");
|
||||||
|
expect(fetch).to.have.been.calledOnceWith("/some/path/fr.json");
|
||||||
|
expect(lclz.get("Foobar")).to.equal("gazonk");
|
||||||
|
});
|
||||||
|
it('should handle base URL without trailing slash', async function () {
|
||||||
|
window.navigator.languages = ["de", "fr"];
|
||||||
|
fetch.resolves(new Response('{ "Foobar": "gazonk" }'));
|
||||||
|
let lclz = new Localizer();
|
||||||
|
await lclz.setup(["ru", "fr"], "/some/path");
|
||||||
|
expect(fetch).to.have.been.calledOnceWith("/some/path/fr.json");
|
||||||
|
expect(lclz.get("Foobar")).to.equal("gazonk");
|
||||||
|
});
|
||||||
|
it('should handle current base URL', async function () {
|
||||||
|
window.navigator.languages = ["de", "fr"];
|
||||||
|
fetch.resolves(new Response('{ "Foobar": "gazonk" }'));
|
||||||
|
let lclz = new Localizer();
|
||||||
|
await lclz.setup(["ru", "fr"]);
|
||||||
|
expect(fetch).to.have.been.calledOnceWith("fr.json");
|
||||||
|
expect(lclz.get("Foobar")).to.equal("gazonk");
|
||||||
|
});
|
||||||
|
it('should fail if dictionary cannot be found', async function () {
|
||||||
|
window.navigator.languages = ["de", "fr"];
|
||||||
|
fetch.resolves(new Response('{}', { status: 404 }));
|
||||||
|
let lclz = new Localizer();
|
||||||
|
let ok = false;
|
||||||
|
try {
|
||||||
|
await lclz.setup(["ru", "fr"], "/some/path/");
|
||||||
|
} catch (e) {
|
||||||
|
ok = true;
|
||||||
|
}
|
||||||
|
expect(ok).to.be.true;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,357 +0,0 @@
|
||||||
const expect = chai.expect;
|
|
||||||
|
|
||||||
import RFB from '../core/rfb.js';
|
|
||||||
|
|
||||||
import FakeWebSocket from './fake.websocket.js';
|
|
||||||
|
|
||||||
function fakeGetRandomValues(arr) {
|
|
||||||
if (arr.length === 16) {
|
|
||||||
arr.set(new Uint8Array([
|
|
||||||
0x1c, 0x08, 0xfe, 0x21, 0x78, 0xef, 0x4e, 0xf9,
|
|
||||||
0x3f, 0x05, 0xec, 0xea, 0xd4, 0x6b, 0xa5, 0xd5,
|
|
||||||
]));
|
|
||||||
} else {
|
|
||||||
arr.set(new Uint8Array([
|
|
||||||
0xee, 0xe2, 0xf1, 0x5a, 0x3c, 0xa7, 0xbe, 0x95,
|
|
||||||
0x6f, 0x2a, 0x75, 0xfd, 0x62, 0x01, 0xcb, 0xbf,
|
|
||||||
0x43, 0x74, 0xca, 0x47, 0x4d, 0xfb, 0x0f, 0xcf,
|
|
||||||
0x3a, 0x6d, 0x55, 0x6b, 0x59, 0x3a, 0xf6, 0x87,
|
|
||||||
0xcb, 0x03, 0xb7, 0x28, 0x35, 0x7b, 0x15, 0x8e,
|
|
||||||
0xb6, 0xc8, 0x8f, 0x2d, 0x5e, 0x7b, 0x1c, 0x9a,
|
|
||||||
0x32, 0x55, 0xe7, 0x64, 0x36, 0x25, 0x7b, 0xa3,
|
|
||||||
0xe9, 0x4f, 0x6f, 0x97, 0xdc, 0xa4, 0xd4, 0x62,
|
|
||||||
0x6d, 0x7f, 0xab, 0x02, 0x6b, 0x13, 0x56, 0x69,
|
|
||||||
0xfb, 0xd0, 0xd4, 0x13, 0x76, 0xcd, 0x0d, 0xd0,
|
|
||||||
0x1f, 0xd1, 0x0c, 0x63, 0x3a, 0x34, 0x20, 0x6c,
|
|
||||||
0xbb, 0x60, 0x45, 0x82, 0x23, 0xfd, 0x7c, 0x77,
|
|
||||||
0x6d, 0xcc, 0x5e, 0xaa, 0xc3, 0x0c, 0x43, 0xb7,
|
|
||||||
0x8d, 0xc0, 0x27, 0x6e, 0xeb, 0x1d, 0x6c, 0x5f,
|
|
||||||
0xd8, 0x1c, 0x3c, 0x1c, 0x60, 0x2e, 0x82, 0x15,
|
|
||||||
0xfd, 0x2e, 0x5f, 0x3a, 0x15, 0x53, 0x14, 0x70,
|
|
||||||
0x4f, 0xe1, 0x65, 0x68, 0x35, 0x6d, 0xc7, 0x64,
|
|
||||||
0xdb, 0xdd, 0x09, 0x31, 0x4f, 0x7b, 0x6d, 0x6c,
|
|
||||||
0x77, 0x59, 0x5e, 0x1e, 0xfa, 0x4b, 0x06, 0x14,
|
|
||||||
0xbe, 0xdc, 0x9c, 0x3d, 0x7b, 0xed, 0xf3, 0x2b,
|
|
||||||
0x19, 0x26, 0x11, 0x8e, 0x3f, 0xab, 0x73, 0x9a,
|
|
||||||
0x0a, 0x3a, 0xaa, 0x85, 0x06, 0xd5, 0xca, 0x3f,
|
|
||||||
0xc3, 0xe2, 0x33, 0x7f, 0x97, 0x74, 0x98, 0x8f,
|
|
||||||
0x2f, 0xa5, 0xfc, 0x7e, 0xb1, 0x77, 0x71, 0x58,
|
|
||||||
0xf0, 0xbc, 0x04, 0x59, 0xbb, 0xb4, 0xc6, 0xcc,
|
|
||||||
0x0f, 0x06, 0xcd, 0xa2, 0xd5, 0x01, 0x2f, 0xb2,
|
|
||||||
0x22, 0x0b, 0xfc, 0x1e, 0x59, 0x9f, 0xd3, 0x4f,
|
|
||||||
0x30, 0x95, 0xc6, 0x80, 0x0f, 0x69, 0xf3, 0x4a,
|
|
||||||
0xd4, 0x36, 0xb6, 0x5a, 0x0b, 0x16, 0x0d, 0x81,
|
|
||||||
0x31, 0xb0, 0x69, 0xd4, 0x4e,
|
|
||||||
]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fakeGeneratekey() {
|
|
||||||
let key = JSON.parse('{"alg":"RSA-OAEP-256","d":"B7QR2yI8sXjo8vQhJpX9odqqR\
|
|
||||||
6wIuPrTM1B1JJEKVeSrr7OYcc1FRJ52Vap9LIAU-ezigs9QDvWMxknB8motLnG69Wck37nt9_z4s8l\
|
|
||||||
FQp0nROA-oaR92HW34KNL1b2fEVWGI0N86h730MvTJC5O2cmKeMezIG-oNqbbfFyP8AW-WLdDlgZm1\
|
|
||||||
1-FjzhbVpb0Bc7nRSgBPSV-EY6Sl-LuglxDx4LaTdQW7QE_WXoRUt-GYGfTseuFQQK5WeoyX3yBtQy\
|
|
||||||
dpauW6rrgyWdtP4hDFIoZsX6w1i-UMWMMwlIB5FdnUSi26igVGADGpV_vGMP36bv-EHp0bY-Qp0gpI\
|
|
||||||
fLfgQ","dp":"Z1v5UceFfV2bhmbG19eGYb30jFxqoRBq36PKNY7IunMs1keYy0FpLbyGhtgMZ1Ymm\
|
|
||||||
c8wEzGYsCPEP-ykcun_rlyu7YxmcnyC9YQqTqLyqvO-7rUqDvk9TMfdqWFP6heADRhKZmEbmcau6_m\
|
|
||||||
2MwwK9kOkMKWvpqp8_TpJMnAH7zE","dq":"OBacRE15aY3NtCR4cvP5os3sT70JbDdDLHT3IHZM6r\
|
|
||||||
E35CYNpLDia2chm_wnMcYvKFW9zC2ajRZ15i9c_VXQzS7ZlTaQYBFyMt7kVhxMEMFsPv1crD6t3uEI\
|
|
||||||
j0LNuNYyy0jkon_LPZKQFK654CiL-L2YaNXOH4HbHP02dWeVQIE","e":"AQAB","ext":true,"ke\
|
|
||||||
y_ops":["decrypt"],"kty":"RSA","n":"m1c92ZFk9ZI6l_O4YFiNxbv0Ng94SB3yThy1P_mcqr\
|
|
||||||
GDQkRiGVdcTxAk38T9PgLztmspF-6U5TAHO-gSmmW88AC9m6f1Mspps6r7zl-M_OG-TwvGzf3BDz8z\
|
|
||||||
Eg1FPbZV7whO1M4TCAZ0PqwG7qCc6nK1WiAhaKrSpzuPdL1igfNBsX7qu5wgw4ZTTGSLbVC_LfULQ5\
|
|
||||||
FADgFTRXUSaxm1F8C_Lwy6a2e4nTcXilmtN2IHUjHegzm-Tq2HizmR3ARdWJpESYIW5-AXoiqj29tD\
|
|
||||||
rqCmu2WPkB2psVp83IzZfaQNQzjNfvA8GpimkcDCkP5VMRrtKCcG4ZAFnO-A3NBX_Q","p":"2Q_lN\
|
|
||||||
L7vCOBzAppYzCZo3WSh0hX-MOZyPUznks5U2TjmfdNZoL6_FJRiGyyLvwSiZFdEAAvpAyESFfFigng\
|
|
||||||
AqMLSf448nPg15VUGj533CotsEM0WpoEr1JCgqdUbgDAfJQIBcwOmegBqd7lWm7uzEnRCvouB70ybk\
|
|
||||||
JfpdprhkVE","q":"tzTt-F3g2u_3Ctj26Ho9iN_wC_W0lXGzslLt5nLmss8JqdLoDDrijjU-gjeRh\
|
|
||||||
7lgiuHdUc3dorfFKbaMNOjoW3QKqt9oZ1JM0HKeRw0X2PnWW_0WK6DK5ASWDTXbMq2sUZqJvYEyL74\
|
|
||||||
H2Zrt0RPAux7XQLEVgND6ROdXnMJ70O0","qi":"qfl4cXQkz4BNqa2De0-PfdU-8d1w3onnaGqx1D\
|
|
||||||
s2fHzD_SJ4cNghn2TksoT9Qo64b3pUjH9igi2pyEjomk6D12N6FG0e10u7vFKv3W5YqUOgTpYdbcWH\
|
|
||||||
dZ2qZWJU0XQZIrF8jLGTOO4GYP6_9sJ5R7Wk_0MdqQy8qvixWD4zLcY"}');
|
|
||||||
key = await window.crypto.subtle.importKey("jwk", key, {
|
|
||||||
name: "RSA-OAEP",
|
|
||||||
hash: {name: "SHA-256"}
|
|
||||||
}, true, ["decrypt"]);
|
|
||||||
return {privateKey: key};
|
|
||||||
}
|
|
||||||
|
|
||||||
const receiveData = new Uint8Array([
|
|
||||||
// server public key
|
|
||||||
0x00, 0x00, 0x08, 0x00, 0xac, 0x1a, 0xbc, 0x42,
|
|
||||||
0x8a, 0x2a, 0x69, 0x65, 0x54, 0xf8, 0x9a, 0xe6,
|
|
||||||
0x43, 0xaa, 0xf7, 0x27, 0xf6, 0x2a, 0xf8, 0x8f,
|
|
||||||
0x36, 0xd4, 0xae, 0x54, 0x0f, 0x16, 0x28, 0x08,
|
|
||||||
0xc2, 0x5b, 0xca, 0x23, 0xdc, 0x27, 0x88, 0x1a,
|
|
||||||
0x12, 0x82, 0xa8, 0x54, 0xea, 0x00, 0x99, 0x8d,
|
|
||||||
0x02, 0x1d, 0x77, 0x4a, 0xeb, 0xd0, 0x93, 0x40,
|
|
||||||
0x79, 0x86, 0xcb, 0x37, 0xd4, 0xb2, 0xc7, 0xcd,
|
|
||||||
0x93, 0xe1, 0x00, 0x4d, 0x86, 0xff, 0x97, 0x33,
|
|
||||||
0x0c, 0xad, 0x51, 0x47, 0x45, 0x85, 0x56, 0x07,
|
|
||||||
0x65, 0x21, 0x7c, 0x57, 0x6d, 0x68, 0x7d, 0xd7,
|
|
||||||
0x00, 0x43, 0x0c, 0x9d, 0x3b, 0xa1, 0x5a, 0x11,
|
|
||||||
0xed, 0x51, 0x77, 0xf9, 0xd1, 0x5b, 0x33, 0xd7,
|
|
||||||
0x1a, 0xeb, 0x65, 0x57, 0xc0, 0x01, 0x51, 0xff,
|
|
||||||
0x9b, 0x82, 0xb3, 0xeb, 0x82, 0xc2, 0x1f, 0xca,
|
|
||||||
0x47, 0xc0, 0x6a, 0x09, 0xe0, 0xf7, 0xda, 0x39,
|
|
||||||
0x85, 0x12, 0xe7, 0x45, 0x8d, 0xb4, 0x1a, 0xda,
|
|
||||||
0xcb, 0x86, 0x58, 0x52, 0x37, 0x66, 0x9d, 0x8a,
|
|
||||||
0xce, 0xf2, 0x18, 0x78, 0x7d, 0x7f, 0xf0, 0x07,
|
|
||||||
0x94, 0x8e, 0x6b, 0x17, 0xd9, 0x00, 0x2a, 0x3a,
|
|
||||||
0xb9, 0xd4, 0x77, 0xde, 0x70, 0x85, 0xc4, 0x3a,
|
|
||||||
0x62, 0x10, 0x02, 0xee, 0xba, 0xd8, 0xc0, 0x62,
|
|
||||||
0xd0, 0x8e, 0xc1, 0x98, 0x19, 0x8e, 0x39, 0x0f,
|
|
||||||
0x3e, 0x1d, 0x61, 0xb1, 0x93, 0x13, 0x59, 0x39,
|
|
||||||
0xcb, 0x96, 0xf2, 0x17, 0xc9, 0xe1, 0x41, 0xd3,
|
|
||||||
0x20, 0xdd, 0x62, 0x5e, 0x7d, 0x53, 0xd6, 0xb7,
|
|
||||||
0x1d, 0xfe, 0x02, 0x18, 0x1f, 0xe0, 0xef, 0x3d,
|
|
||||||
0x94, 0xe3, 0x0a, 0x9c, 0x59, 0x54, 0xd8, 0x98,
|
|
||||||
0x16, 0x9c, 0x31, 0xda, 0x41, 0x0f, 0x2e, 0x71,
|
|
||||||
0x68, 0xe0, 0xa2, 0x62, 0x3e, 0xe5, 0x25, 0x31,
|
|
||||||
0xcf, 0xfc, 0x67, 0x63, 0xc3, 0xb0, 0xda, 0x3f,
|
|
||||||
0x7b, 0x59, 0xbe, 0x7e, 0x9e, 0xa8, 0xd0, 0x01,
|
|
||||||
0x4f, 0x43, 0x7f, 0x8d, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x01, 0x00, 0x01,
|
|
||||||
// server random
|
|
||||||
0x01, 0x00, 0x5b, 0x58, 0x2a, 0x96, 0x2d, 0xbb,
|
|
||||||
0x88, 0xec, 0xc3, 0x54, 0x00, 0xf3, 0xbb, 0xbe,
|
|
||||||
0x17, 0xa3, 0x84, 0xd3, 0xef, 0xd8, 0x4a, 0x31,
|
|
||||||
0x09, 0x20, 0xdd, 0xbc, 0x16, 0x9d, 0xc9, 0x5b,
|
|
||||||
0x99, 0x62, 0x86, 0xfe, 0x0b, 0x28, 0x4b, 0xfe,
|
|
||||||
0x5b, 0x56, 0x2d, 0xcb, 0x6e, 0x6f, 0xec, 0xf0,
|
|
||||||
0x53, 0x0c, 0x33, 0x84, 0x93, 0xc9, 0xbf, 0x79,
|
|
||||||
0xde, 0xb3, 0xb9, 0x29, 0x60, 0x78, 0xde, 0xe6,
|
|
||||||
0x1d, 0xa7, 0x89, 0x48, 0x3f, 0xd1, 0x58, 0x66,
|
|
||||||
0x27, 0x9c, 0xd4, 0x6e, 0x72, 0x9c, 0x6e, 0x4a,
|
|
||||||
0xc0, 0x69, 0x79, 0x6f, 0x79, 0x0f, 0x13, 0xc4,
|
|
||||||
0x20, 0xcf, 0xa6, 0xbb, 0xce, 0x18, 0x6d, 0xd5,
|
|
||||||
0x9e, 0xd9, 0x67, 0xbe, 0x61, 0x43, 0x67, 0x11,
|
|
||||||
0x76, 0x2f, 0xfd, 0x78, 0x75, 0x2b, 0x89, 0x35,
|
|
||||||
0xdd, 0x0f, 0x13, 0x7f, 0xee, 0x78, 0xad, 0x32,
|
|
||||||
0x56, 0x21, 0x81, 0x08, 0x1f, 0xcf, 0x4c, 0x29,
|
|
||||||
0xa3, 0xeb, 0x89, 0x2d, 0xbe, 0xba, 0x8d, 0xe4,
|
|
||||||
0x69, 0x28, 0xba, 0x53, 0x82, 0xce, 0x5c, 0xf6,
|
|
||||||
0x5e, 0x5e, 0xa5, 0xb3, 0x88, 0xd8, 0x3d, 0xab,
|
|
||||||
0xf4, 0x24, 0x9e, 0x3f, 0x04, 0xaf, 0xdc, 0x48,
|
|
||||||
0x90, 0x53, 0x37, 0xe6, 0x82, 0x1d, 0xe0, 0x15,
|
|
||||||
0x91, 0xa1, 0xc6, 0xa9, 0x54, 0xe5, 0x2a, 0xb5,
|
|
||||||
0x64, 0x2d, 0x93, 0xc0, 0xc0, 0xe1, 0x0f, 0x6a,
|
|
||||||
0x4b, 0xdb, 0x77, 0xf8, 0x4a, 0x0f, 0x83, 0x36,
|
|
||||||
0xdd, 0x5e, 0x1e, 0xdd, 0x39, 0x65, 0xa2, 0x11,
|
|
||||||
0xc2, 0xcf, 0x56, 0x1e, 0xa1, 0x29, 0xae, 0x11,
|
|
||||||
0x9f, 0x3a, 0x82, 0xc7, 0xbd, 0x89, 0x6e, 0x59,
|
|
||||||
0xb8, 0x59, 0x17, 0xcb, 0x65, 0xa0, 0x4b, 0x4d,
|
|
||||||
0xbe, 0x33, 0x32, 0x85, 0x9c, 0xca, 0x5e, 0x95,
|
|
||||||
0xc2, 0x5a, 0xd0, 0xc9, 0x8b, 0xf1, 0xf5, 0x14,
|
|
||||||
0xcf, 0x76, 0x80, 0xc2, 0x24, 0x0a, 0x39, 0x7e,
|
|
||||||
0x60, 0x64, 0xce, 0xd9, 0xb8, 0xad, 0x24, 0xa8,
|
|
||||||
0xdf, 0xcb,
|
|
||||||
// server hash
|
|
||||||
0x00, 0x14, 0x39, 0x30, 0x66, 0xb5, 0x66, 0x8a,
|
|
||||||
0xcd, 0xb9, 0xda, 0xe0, 0xde, 0xcb, 0xf6, 0x47,
|
|
||||||
0x5f, 0x54, 0x66, 0xe0, 0xbc, 0x49, 0x37, 0x01,
|
|
||||||
0xf2, 0x9e, 0xef, 0xcc, 0xcd, 0x4d, 0x6c, 0x0e,
|
|
||||||
0xc6, 0xab, 0x28, 0xd4, 0x7b, 0x13,
|
|
||||||
// subtype
|
|
||||||
0x00, 0x01, 0x30, 0x2a, 0xc3, 0x0b, 0xc2, 0x1c,
|
|
||||||
0xeb, 0x02, 0x44, 0x92, 0x5d, 0xfd, 0xf9, 0xa7,
|
|
||||||
0x94, 0xd0, 0x19,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const sendData = new Uint8Array([
|
|
||||||
// client public key
|
|
||||||
0x00, 0x00, 0x08, 0x00, 0x9b, 0x57, 0x3d, 0xd9,
|
|
||||||
0x91, 0x64, 0xf5, 0x92, 0x3a, 0x97, 0xf3, 0xb8,
|
|
||||||
0x60, 0x58, 0x8d, 0xc5, 0xbb, 0xf4, 0x36, 0x0f,
|
|
||||||
0x78, 0x48, 0x1d, 0xf2, 0x4e, 0x1c, 0xb5, 0x3f,
|
|
||||||
0xf9, 0x9c, 0xaa, 0xb1, 0x83, 0x42, 0x44, 0x62,
|
|
||||||
0x19, 0x57, 0x5c, 0x4f, 0x10, 0x24, 0xdf, 0xc4,
|
|
||||||
0xfd, 0x3e, 0x02, 0xf3, 0xb6, 0x6b, 0x29, 0x17,
|
|
||||||
0xee, 0x94, 0xe5, 0x30, 0x07, 0x3b, 0xe8, 0x12,
|
|
||||||
0x9a, 0x65, 0xbc, 0xf0, 0x00, 0xbd, 0x9b, 0xa7,
|
|
||||||
0xf5, 0x32, 0xca, 0x69, 0xb3, 0xaa, 0xfb, 0xce,
|
|
||||||
0x5f, 0x8c, 0xfc, 0xe1, 0xbe, 0x4f, 0x0b, 0xc6,
|
|
||||||
0xcd, 0xfd, 0xc1, 0x0f, 0x3f, 0x33, 0x12, 0x0d,
|
|
||||||
0x45, 0x3d, 0xb6, 0x55, 0xef, 0x08, 0x4e, 0xd4,
|
|
||||||
0xce, 0x13, 0x08, 0x06, 0x74, 0x3e, 0xac, 0x06,
|
|
||||||
0xee, 0xa0, 0x9c, 0xea, 0x72, 0xb5, 0x5a, 0x20,
|
|
||||||
0x21, 0x68, 0xaa, 0xd2, 0xa7, 0x3b, 0x8f, 0x74,
|
|
||||||
0xbd, 0x62, 0x81, 0xf3, 0x41, 0xb1, 0x7e, 0xea,
|
|
||||||
0xbb, 0x9c, 0x20, 0xc3, 0x86, 0x53, 0x4c, 0x64,
|
|
||||||
0x8b, 0x6d, 0x50, 0xbf, 0x2d, 0xf5, 0x0b, 0x43,
|
|
||||||
0x91, 0x40, 0x0e, 0x01, 0x53, 0x45, 0x75, 0x12,
|
|
||||||
0x6b, 0x19, 0xb5, 0x17, 0xc0, 0xbf, 0x2f, 0x0c,
|
|
||||||
0xba, 0x6b, 0x67, 0xb8, 0x9d, 0x37, 0x17, 0x8a,
|
|
||||||
0x59, 0xad, 0x37, 0x62, 0x07, 0x52, 0x31, 0xde,
|
|
||||||
0x83, 0x39, 0xbe, 0x4e, 0xad, 0x87, 0x8b, 0x39,
|
|
||||||
0x91, 0xdc, 0x04, 0x5d, 0x58, 0x9a, 0x44, 0x49,
|
|
||||||
0x82, 0x16, 0xe7, 0xe0, 0x17, 0xa2, 0x2a, 0xa3,
|
|
||||||
0xdb, 0xdb, 0x43, 0xae, 0xa0, 0xa6, 0xbb, 0x65,
|
|
||||||
0x8f, 0x90, 0x1d, 0xa9, 0xb1, 0x5a, 0x7c, 0xdc,
|
|
||||||
0x8c, 0xd9, 0x7d, 0xa4, 0x0d, 0x43, 0x38, 0xcd,
|
|
||||||
0x7e, 0xf0, 0x3c, 0x1a, 0x98, 0xa6, 0x91, 0xc0,
|
|
||||||
0xc2, 0x90, 0xfe, 0x55, 0x31, 0x1a, 0xed, 0x28,
|
|
||||||
0x27, 0x06, 0xe1, 0x90, 0x05, 0x9c, 0xef, 0x80,
|
|
||||||
0xdc, 0xd0, 0x57, 0xfd, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x01, 0x00, 0x01,
|
|
||||||
// client random
|
|
||||||
0x01, 0x00, 0x84, 0x7f, 0x26, 0x54, 0x74, 0xf6,
|
|
||||||
0x47, 0xaf, 0x33, 0x64, 0x0d, 0xa6, 0xe5, 0x30,
|
|
||||||
0xba, 0xe6, 0xe4, 0x8e, 0x50, 0x40, 0x71, 0x1c,
|
|
||||||
0x0e, 0x06, 0x63, 0xf5, 0x07, 0x2a, 0x26, 0x68,
|
|
||||||
0xd6, 0xcf, 0xa6, 0x80, 0x84, 0x5e, 0x64, 0xd4,
|
|
||||||
0x5e, 0x62, 0x31, 0xfe, 0x44, 0x51, 0x0b, 0x7c,
|
|
||||||
0x4d, 0x55, 0xc5, 0x4a, 0x7e, 0x0d, 0x4d, 0x9b,
|
|
||||||
0x84, 0xb4, 0x32, 0x2b, 0x4d, 0x8a, 0x34, 0x8d,
|
|
||||||
0xc8, 0xcf, 0x19, 0x3b, 0x64, 0x82, 0x27, 0x9e,
|
|
||||||
0xa7, 0x70, 0x2a, 0xc1, 0xb8, 0xf3, 0x6a, 0x3a,
|
|
||||||
0xf2, 0x75, 0x6e, 0x1d, 0xeb, 0xb6, 0x70, 0x7a,
|
|
||||||
0x15, 0x18, 0x38, 0x00, 0xb4, 0x4f, 0x55, 0xb5,
|
|
||||||
0xd8, 0x03, 0x4e, 0xb8, 0x53, 0xff, 0x80, 0x62,
|
|
||||||
0xf1, 0x9d, 0x27, 0xe8, 0x2a, 0x3d, 0x98, 0x19,
|
|
||||||
0x32, 0x09, 0x7e, 0x9a, 0xb0, 0xc7, 0x46, 0x23,
|
|
||||||
0x10, 0x85, 0x35, 0x00, 0x96, 0xce, 0xb3, 0x2c,
|
|
||||||
0x84, 0x8d, 0xf4, 0x9e, 0xa8, 0x42, 0x67, 0xed,
|
|
||||||
0x09, 0xa6, 0x09, 0x97, 0xb3, 0x64, 0x26, 0xfb,
|
|
||||||
0x71, 0x11, 0x9b, 0x3f, 0xbb, 0x57, 0xb8, 0x5b,
|
|
||||||
0x2e, 0xc5, 0x2d, 0x8c, 0x5c, 0xf7, 0xef, 0x27,
|
|
||||||
0x25, 0x88, 0x42, 0x45, 0x43, 0xa4, 0xe7, 0xde,
|
|
||||||
0xea, 0xf9, 0x15, 0x7b, 0x5d, 0x66, 0x24, 0xce,
|
|
||||||
0xf7, 0xc8, 0x2f, 0xc5, 0xc0, 0x3d, 0xcd, 0xf2,
|
|
||||||
0x62, 0xfc, 0x1a, 0x5e, 0xec, 0xff, 0xf1, 0x1b,
|
|
||||||
0xc8, 0xdb, 0xc1, 0x0f, 0x54, 0x66, 0x9e, 0xfd,
|
|
||||||
0x99, 0x9b, 0x23, 0x70, 0x62, 0x37, 0x80, 0xad,
|
|
||||||
0x91, 0x6b, 0x84, 0x85, 0x6a, 0x4c, 0x80, 0x9e,
|
|
||||||
0x60, 0x8a, 0x93, 0xa3, 0xc8, 0x8e, 0xc4, 0x4b,
|
|
||||||
0x4d, 0xb4, 0x8e, 0x3e, 0xaf, 0xce, 0xcd, 0x83,
|
|
||||||
0xe5, 0x21, 0x90, 0x95, 0x20, 0x3c, 0x82, 0xb4,
|
|
||||||
0x7c, 0xab, 0x63, 0x9c, 0xae, 0xc3, 0xc9, 0x71,
|
|
||||||
0x1a, 0xec, 0x34, 0x18, 0x47, 0xec, 0x5c, 0x4d,
|
|
||||||
0xed, 0x84,
|
|
||||||
// client hash
|
|
||||||
0x00, 0x14, 0x9c, 0x91, 0x9e, 0x76, 0xcf, 0x1e,
|
|
||||||
0x66, 0x87, 0x5e, 0x29, 0xf1, 0x13, 0x80, 0xea,
|
|
||||||
0x7d, 0xec, 0xae, 0xf9, 0x60, 0x01, 0xd3, 0x6f,
|
|
||||||
0xb7, 0x9e, 0xb2, 0xcd, 0x2d, 0xc8, 0xf8, 0x84,
|
|
||||||
0xb2, 0x9f, 0xc3, 0x7e, 0xb4, 0xbe,
|
|
||||||
// credentials
|
|
||||||
0x00, 0x08, 0x9d, 0xc8, 0x3a, 0xb8, 0x80, 0x4f,
|
|
||||||
0xe3, 0x52, 0xdb, 0x62, 0x9e, 0x97, 0x64, 0x82,
|
|
||||||
0xa8, 0xa1, 0x6b, 0x7e, 0x4d, 0x68, 0x8c, 0x29,
|
|
||||||
0x91, 0x38,
|
|
||||||
]);
|
|
||||||
|
|
||||||
describe('RA2 handshake', function () {
|
|
||||||
let sock;
|
|
||||||
let rfb;
|
|
||||||
let sentData;
|
|
||||||
|
|
||||||
before(() => {
|
|
||||||
FakeWebSocket.replace();
|
|
||||||
sinon.stub(window.crypto, "getRandomValues").callsFake(fakeGetRandomValues);
|
|
||||||
sinon.stub(window.crypto.subtle, "generateKey").callsFake(fakeGeneratekey);
|
|
||||||
});
|
|
||||||
after(() => {
|
|
||||||
FakeWebSocket.restore();
|
|
||||||
window.crypto.getRandomValues.restore();
|
|
||||||
window.crypto.subtle.generateKey.restore();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should fire the serververification event', function (done) {
|
|
||||||
sentData = new Uint8Array();
|
|
||||||
rfb = new RFB(document.createElement('div'), "ws://example.com");
|
|
||||||
sock = rfb._sock;
|
|
||||||
sock.send = (data) => {
|
|
||||||
let res = new Uint8Array(sentData.length + data.length);
|
|
||||||
res.set(sentData);
|
|
||||||
res.set(data, sentData.length);
|
|
||||||
sentData = res;
|
|
||||||
};
|
|
||||||
rfb._rfbInitState = "Security";
|
|
||||||
rfb._rfbVersion = 3.8;
|
|
||||||
sock._websocket._receiveData(new Uint8Array([1, 6]));
|
|
||||||
rfb.addEventListener("serververification", (e) => {
|
|
||||||
expect(e.detail.publickey).to.eql(receiveData.slice(0, 516));
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
sock._websocket._receiveData(receiveData);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle approveServer and fire the credentialsrequired event', function (done) {
|
|
||||||
rfb.addEventListener("credentialsrequired", (e) => {
|
|
||||||
expect(e.detail.types).to.eql(["password"]);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
rfb.approveServer();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should match sendData after sending credentials', function (done) {
|
|
||||||
rfb.addEventListener("securityresult", (event) => {
|
|
||||||
expect(sentData.slice(1)).to.eql(sendData);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
rfb.sendCredentials({ "password": "123456" });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -9,23 +9,26 @@ import FakeWebSocket from './fake.websocket.js';
|
||||||
|
|
||||||
function testDecodeRect(decoder, x, y, width, height, data, display, depth) {
|
function testDecodeRect(decoder, x, y, width, height, data, display, depth) {
|
||||||
let sock;
|
let sock;
|
||||||
|
let done = false;
|
||||||
|
|
||||||
sock = new Websock;
|
sock = new Websock;
|
||||||
sock.open("ws://example.com");
|
sock.open("ws://example.com");
|
||||||
|
|
||||||
sock.on('message', () => {
|
sock.on('message', () => {
|
||||||
decoder.decodeRect(x, y, width, height, sock, display, depth);
|
done = decoder.decodeRect(x, y, width, height, sock, display, depth);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Empty messages are filtered at multiple layers, so we need to
|
// Empty messages are filtered at multiple layers, so we need to
|
||||||
// do a direct call
|
// do a direct call
|
||||||
if (data.length === 0) {
|
if (data.length === 0) {
|
||||||
decoder.decodeRect(x, y, width, height, sock, display, depth);
|
done = decoder.decodeRect(x, y, width, height, sock, display, depth);
|
||||||
} else {
|
} else {
|
||||||
sock._websocket._receiveData(new Uint8Array(data));
|
sock._websocket._receiveData(new Uint8Array(data));
|
||||||
}
|
}
|
||||||
|
|
||||||
display.flip();
|
display.flip();
|
||||||
|
|
||||||
|
return done;
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('Raw Decoder', function () {
|
describe('Raw Decoder', function () {
|
||||||
|
|
@ -42,22 +45,36 @@ describe('Raw Decoder', function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle the Raw encoding', function () {
|
it('should handle the Raw encoding', function () {
|
||||||
testDecodeRect(decoder, 0, 0, 2, 2,
|
let done;
|
||||||
[0xff, 0x00, 0x00, 0, 0x00, 0xff, 0x00, 0,
|
|
||||||
0x00, 0xff, 0x00, 0, 0xff, 0x00, 0x00, 0],
|
done = testDecodeRect(decoder, 0, 0, 2, 2,
|
||||||
display, 24);
|
[0xff, 0x00, 0x00, 0,
|
||||||
testDecodeRect(decoder, 2, 0, 2, 2,
|
0x00, 0xff, 0x00, 0,
|
||||||
[0x00, 0x00, 0xff, 0, 0x00, 0x00, 0xff, 0,
|
0x00, 0xff, 0x00, 0,
|
||||||
0x00, 0x00, 0xff, 0, 0x00, 0x00, 0xff, 0],
|
0xff, 0x00, 0x00, 0],
|
||||||
display, 24);
|
display, 24);
|
||||||
testDecodeRect(decoder, 0, 2, 4, 1,
|
expect(done).to.be.true;
|
||||||
[0xee, 0x00, 0xff, 0, 0x00, 0xee, 0xff, 0,
|
done = testDecodeRect(decoder, 2, 0, 2, 2,
|
||||||
0xaa, 0xee, 0xff, 0, 0xab, 0xee, 0xff, 0],
|
[0x00, 0x00, 0xff, 0,
|
||||||
display, 24);
|
0x00, 0x00, 0xff, 0,
|
||||||
testDecodeRect(decoder, 0, 3, 4, 1,
|
0x00, 0x00, 0xff, 0,
|
||||||
[0xee, 0x00, 0xff, 0, 0x00, 0xee, 0xff, 0,
|
0x00, 0x00, 0xff, 0],
|
||||||
0xaa, 0xee, 0xff, 0, 0xab, 0xee, 0xff, 0],
|
display, 24);
|
||||||
display, 24);
|
expect(done).to.be.true;
|
||||||
|
done = testDecodeRect(decoder, 0, 2, 4, 1,
|
||||||
|
[0xee, 0x00, 0xff, 0,
|
||||||
|
0x00, 0xee, 0xff, 0,
|
||||||
|
0xaa, 0xee, 0xff, 0,
|
||||||
|
0xab, 0xee, 0xff, 0],
|
||||||
|
display, 24);
|
||||||
|
expect(done).to.be.true;
|
||||||
|
done = testDecodeRect(decoder, 0, 3, 4, 1,
|
||||||
|
[0xee, 0x00, 0xff, 0,
|
||||||
|
0x00, 0xee, 0xff, 0,
|
||||||
|
0xaa, 0xee, 0xff, 0,
|
||||||
|
0xab, 0xee, 0xff, 0],
|
||||||
|
display, 24);
|
||||||
|
expect(done).to.be.true;
|
||||||
|
|
||||||
let targetData = new Uint8Array([
|
let targetData = new Uint8Array([
|
||||||
0xff, 0x00, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
|
0xff, 0x00, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
|
||||||
|
|
@ -70,18 +87,24 @@ describe('Raw Decoder', function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle the Raw encoding in low colour mode', function () {
|
it('should handle the Raw encoding in low colour mode', function () {
|
||||||
testDecodeRect(decoder, 0, 0, 2, 2,
|
let done;
|
||||||
[0x30, 0x30, 0x30, 0x30],
|
|
||||||
display, 8);
|
done = testDecodeRect(decoder, 0, 0, 2, 2,
|
||||||
testDecodeRect(decoder, 2, 0, 2, 2,
|
[0x30, 0x30, 0x30, 0x30],
|
||||||
[0x0c, 0x0c, 0x0c, 0x0c],
|
display, 8);
|
||||||
display, 8);
|
expect(done).to.be.true;
|
||||||
testDecodeRect(decoder, 0, 2, 4, 1,
|
done = testDecodeRect(decoder, 2, 0, 2, 2,
|
||||||
[0x0c, 0x0c, 0x30, 0x30],
|
[0x0c, 0x0c, 0x0c, 0x0c],
|
||||||
display, 8);
|
display, 8);
|
||||||
testDecodeRect(decoder, 0, 3, 4, 1,
|
expect(done).to.be.true;
|
||||||
[0x0c, 0x0c, 0x30, 0x30],
|
done = testDecodeRect(decoder, 0, 2, 4, 1,
|
||||||
display, 8);
|
[0x0c, 0x0c, 0x30, 0x30],
|
||||||
|
display, 8);
|
||||||
|
expect(done).to.be.true;
|
||||||
|
done = testDecodeRect(decoder, 0, 3, 4, 1,
|
||||||
|
[0x0c, 0x0c, 0x30, 0x30],
|
||||||
|
display, 8);
|
||||||
|
expect(done).to.be.true;
|
||||||
|
|
||||||
let targetData = new Uint8Array([
|
let targetData = new Uint8Array([
|
||||||
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
|
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
|
||||||
|
|
@ -98,7 +121,7 @@ describe('Raw Decoder', function () {
|
||||||
display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]);
|
display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]);
|
||||||
display.fillRect(0, 2, 2, 2, [ 0x00, 0xff, 0x00 ]);
|
display.fillRect(0, 2, 2, 2, [ 0x00, 0xff, 0x00 ]);
|
||||||
|
|
||||||
testDecodeRect(decoder, 1, 2, 0, 0, [], display, 24);
|
let done = testDecodeRect(decoder, 1, 2, 0, 0, [], display, 24);
|
||||||
|
|
||||||
let targetData = new Uint8Array([
|
let targetData = new Uint8Array([
|
||||||
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
|
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
|
||||||
|
|
@ -107,6 +130,7 @@ describe('Raw Decoder', function () {
|
||||||
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
|
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
expect(done).to.be.true;
|
||||||
expect(display).to.have.displayed(targetData);
|
expect(display).to.have.displayed(targetData);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -115,7 +139,7 @@ describe('Raw Decoder', function () {
|
||||||
display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]);
|
display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]);
|
||||||
display.fillRect(0, 2, 2, 2, [ 0x00, 0xff, 0x00 ]);
|
display.fillRect(0, 2, 2, 2, [ 0x00, 0xff, 0x00 ]);
|
||||||
|
|
||||||
testDecodeRect(decoder, 1, 2, 0, 0, [], display, 8);
|
let done = testDecodeRect(decoder, 1, 2, 0, 0, [], display, 8);
|
||||||
|
|
||||||
let targetData = new Uint8Array([
|
let targetData = new Uint8Array([
|
||||||
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
|
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
|
||||||
|
|
@ -124,6 +148,7 @@ describe('Raw Decoder', function () {
|
||||||
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
|
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
expect(done).to.be.true;
|
||||||
expect(display).to.have.displayed(targetData);
|
expect(display).to.have.displayed(targetData);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
1353
tests/test.rfb.js
1353
tests/test.rfb.js
File diff suppressed because it is too large
Load Diff
|
|
@ -9,23 +9,26 @@ import FakeWebSocket from './fake.websocket.js';
|
||||||
|
|
||||||
function testDecodeRect(decoder, x, y, width, height, data, display, depth) {
|
function testDecodeRect(decoder, x, y, width, height, data, display, depth) {
|
||||||
let sock;
|
let sock;
|
||||||
|
let done = false;
|
||||||
|
|
||||||
sock = new Websock;
|
sock = new Websock;
|
||||||
sock.open("ws://example.com");
|
sock.open("ws://example.com");
|
||||||
|
|
||||||
sock.on('message', () => {
|
sock.on('message', () => {
|
||||||
decoder.decodeRect(x, y, width, height, sock, display, depth);
|
done = decoder.decodeRect(x, y, width, height, sock, display, depth);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Empty messages are filtered at multiple layers, so we need to
|
// Empty messages are filtered at multiple layers, so we need to
|
||||||
// do a direct call
|
// do a direct call
|
||||||
if (data.length === 0) {
|
if (data.length === 0) {
|
||||||
decoder.decodeRect(x, y, width, height, sock, display, depth);
|
done = decoder.decodeRect(x, y, width, height, sock, display, depth);
|
||||||
} else {
|
} else {
|
||||||
sock._websocket._receiveData(new Uint8Array(data));
|
sock._websocket._receiveData(new Uint8Array(data));
|
||||||
}
|
}
|
||||||
|
|
||||||
display.flip();
|
display.flip();
|
||||||
|
|
||||||
|
return done;
|
||||||
}
|
}
|
||||||
|
|
||||||
function push16(arr, num) {
|
function push16(arr, num) {
|
||||||
|
|
@ -76,7 +79,7 @@ describe('RRE Decoder', function () {
|
||||||
push16(data, 2); // width: 2
|
push16(data, 2); // width: 2
|
||||||
push16(data, 2); // height: 2
|
push16(data, 2); // height: 2
|
||||||
|
|
||||||
testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
|
let done = testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
|
||||||
|
|
||||||
let targetData = new Uint8Array([
|
let targetData = new Uint8Array([
|
||||||
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
|
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
|
||||||
|
|
@ -85,6 +88,7 @@ describe('RRE Decoder', function () {
|
||||||
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
|
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
expect(done).to.be.true;
|
||||||
expect(display).to.have.displayed(targetData);
|
expect(display).to.have.displayed(targetData);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -93,7 +97,10 @@ describe('RRE Decoder', function () {
|
||||||
display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]);
|
display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]);
|
||||||
display.fillRect(0, 2, 2, 2, [ 0x00, 0xff, 0x00 ]);
|
display.fillRect(0, 2, 2, 2, [ 0x00, 0xff, 0x00 ]);
|
||||||
|
|
||||||
testDecodeRect(decoder, 1, 2, 0, 0, [ 0x00, 0xff, 0xff, 0xff, 0xff ], display, 24);
|
let done = testDecodeRect(decoder, 1, 2, 0, 0,
|
||||||
|
[ 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0xff, 0xff, 0xff, 0xff ],
|
||||||
|
display, 24);
|
||||||
|
|
||||||
let targetData = new Uint8Array([
|
let targetData = new Uint8Array([
|
||||||
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
|
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
|
||||||
|
|
@ -102,6 +109,7 @@ describe('RRE Decoder', function () {
|
||||||
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
|
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
expect(done).to.be.true;
|
||||||
expect(display).to.have.displayed(targetData);
|
expect(display).to.have.displayed(targetData);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -9,23 +9,26 @@ import FakeWebSocket from './fake.websocket.js';
|
||||||
|
|
||||||
function testDecodeRect(decoder, x, y, width, height, data, display, depth) {
|
function testDecodeRect(decoder, x, y, width, height, data, display, depth) {
|
||||||
let sock;
|
let sock;
|
||||||
|
let done = false;
|
||||||
|
|
||||||
sock = new Websock;
|
sock = new Websock;
|
||||||
sock.open("ws://example.com");
|
sock.open("ws://example.com");
|
||||||
|
|
||||||
sock.on('message', () => {
|
sock.on('message', () => {
|
||||||
decoder.decodeRect(x, y, width, height, sock, display, depth);
|
done = decoder.decodeRect(x, y, width, height, sock, display, depth);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Empty messages are filtered at multiple layers, so we need to
|
// Empty messages are filtered at multiple layers, so we need to
|
||||||
// do a direct call
|
// do a direct call
|
||||||
if (data.length === 0) {
|
if (data.length === 0) {
|
||||||
decoder.decodeRect(x, y, width, height, sock, display, depth);
|
done = decoder.decodeRect(x, y, width, height, sock, display, depth);
|
||||||
} else {
|
} else {
|
||||||
sock._websocket._receiveData(new Uint8Array(data));
|
sock._websocket._receiveData(new Uint8Array(data));
|
||||||
}
|
}
|
||||||
|
|
||||||
display.flip();
|
display.flip();
|
||||||
|
|
||||||
|
return done;
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('Tight Decoder', function () {
|
describe('Tight Decoder', function () {
|
||||||
|
|
@ -42,9 +45,9 @@ describe('Tight Decoder', function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle fill rects', function () {
|
it('should handle fill rects', function () {
|
||||||
testDecodeRect(decoder, 0, 0, 4, 4,
|
let done = testDecodeRect(decoder, 0, 0, 4, 4,
|
||||||
[0x80, 0xff, 0x88, 0x44],
|
[0x80, 0xff, 0x88, 0x44],
|
||||||
display, 24);
|
display, 24);
|
||||||
|
|
||||||
let targetData = new Uint8Array([
|
let targetData = new Uint8Array([
|
||||||
0xff, 0x88, 0x44, 255, 0xff, 0x88, 0x44, 255, 0xff, 0x88, 0x44, 255, 0xff, 0x88, 0x44, 255,
|
0xff, 0x88, 0x44, 255, 0xff, 0x88, 0x44, 255, 0xff, 0x88, 0x44, 255, 0xff, 0x88, 0x44, 255,
|
||||||
|
|
@ -53,21 +56,31 @@ describe('Tight Decoder', function () {
|
||||||
0xff, 0x88, 0x44, 255, 0xff, 0x88, 0x44, 255, 0xff, 0x88, 0x44, 255, 0xff, 0x88, 0x44, 255,
|
0xff, 0x88, 0x44, 255, 0xff, 0x88, 0x44, 255, 0xff, 0x88, 0x44, 255, 0xff, 0x88, 0x44, 255,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
expect(done).to.be.true;
|
||||||
expect(display).to.have.displayed(targetData);
|
expect(display).to.have.displayed(targetData);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle uncompressed copy rects', function () {
|
it('should handle uncompressed copy rects', function () {
|
||||||
|
let done;
|
||||||
let blueData = [ 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0xff ];
|
let blueData = [ 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0xff ];
|
||||||
let greenData = [ 0x00, 0x00, 0xff, 0x00, 0x00, 0xff, 0x00 ];
|
let greenData = [ 0x00, 0x00, 0xff, 0x00, 0x00, 0xff, 0x00 ];
|
||||||
|
|
||||||
testDecodeRect(decoder, 0, 0, 2, 1, blueData, display, 24);
|
done = testDecodeRect(decoder, 0, 0, 2, 1, blueData, display, 24);
|
||||||
testDecodeRect(decoder, 0, 1, 2, 1, blueData, display, 24);
|
expect(done).to.be.true;
|
||||||
testDecodeRect(decoder, 2, 0, 2, 1, greenData, display, 24);
|
done = testDecodeRect(decoder, 0, 1, 2, 1, blueData, display, 24);
|
||||||
testDecodeRect(decoder, 2, 1, 2, 1, greenData, display, 24);
|
expect(done).to.be.true;
|
||||||
testDecodeRect(decoder, 0, 2, 2, 1, greenData, display, 24);
|
done = testDecodeRect(decoder, 2, 0, 2, 1, greenData, display, 24);
|
||||||
testDecodeRect(decoder, 0, 3, 2, 1, greenData, display, 24);
|
expect(done).to.be.true;
|
||||||
testDecodeRect(decoder, 2, 2, 2, 1, blueData, display, 24);
|
done = testDecodeRect(decoder, 2, 1, 2, 1, greenData, display, 24);
|
||||||
testDecodeRect(decoder, 2, 3, 2, 1, blueData, display, 24);
|
expect(done).to.be.true;
|
||||||
|
done = testDecodeRect(decoder, 0, 2, 2, 1, greenData, display, 24);
|
||||||
|
expect(done).to.be.true;
|
||||||
|
done = testDecodeRect(decoder, 0, 3, 2, 1, greenData, display, 24);
|
||||||
|
expect(done).to.be.true;
|
||||||
|
done = testDecodeRect(decoder, 2, 2, 2, 1, blueData, display, 24);
|
||||||
|
expect(done).to.be.true;
|
||||||
|
done = testDecodeRect(decoder, 2, 3, 2, 1, blueData, display, 24);
|
||||||
|
expect(done).to.be.true;
|
||||||
|
|
||||||
let targetData = new Uint8Array([
|
let targetData = new Uint8Array([
|
||||||
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
|
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
|
||||||
|
|
@ -89,7 +102,7 @@ describe('Tight Decoder', function () {
|
||||||
0x60, 0x82, 0x01, 0x99, 0x8d, 0x29, 0x02, 0xa6,
|
0x60, 0x82, 0x01, 0x99, 0x8d, 0x29, 0x02, 0xa6,
|
||||||
0x00, 0x7e, 0xbf, 0x0f, 0xf1 ];
|
0x00, 0x7e, 0xbf, 0x0f, 0xf1 ];
|
||||||
|
|
||||||
testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
|
let done = testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
|
||||||
|
|
||||||
let targetData = new Uint8Array([
|
let targetData = new Uint8Array([
|
||||||
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
|
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
|
||||||
|
|
@ -98,6 +111,7 @@ describe('Tight Decoder', function () {
|
||||||
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
|
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
expect(done).to.be.true;
|
||||||
expect(display).to.have.displayed(targetData);
|
expect(display).to.have.displayed(targetData);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -110,7 +124,7 @@ describe('Tight Decoder', function () {
|
||||||
// Pixels
|
// Pixels
|
||||||
0x30, 0x30, 0xc0, 0xc0 ];
|
0x30, 0x30, 0xc0, 0xc0 ];
|
||||||
|
|
||||||
testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
|
let done = testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
|
||||||
|
|
||||||
let targetData = new Uint8Array([
|
let targetData = new Uint8Array([
|
||||||
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
|
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
|
||||||
|
|
@ -119,6 +133,7 @@ describe('Tight Decoder', function () {
|
||||||
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
|
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
expect(done).to.be.true;
|
||||||
expect(display).to.have.displayed(targetData);
|
expect(display).to.have.displayed(targetData);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -135,7 +150,7 @@ describe('Tight Decoder', function () {
|
||||||
0x78, 0x9c, 0x33, 0x30, 0x38, 0x70, 0xc0, 0x00,
|
0x78, 0x9c, 0x33, 0x30, 0x38, 0x70, 0xc0, 0x00,
|
||||||
0x8a, 0x01, 0x21, 0x3c, 0x05, 0xa1 ];
|
0x8a, 0x01, 0x21, 0x3c, 0x05, 0xa1 ];
|
||||||
|
|
||||||
testDecodeRect(decoder, 0, 0, 4, 12, data, display, 24);
|
let done = testDecodeRect(decoder, 0, 0, 4, 12, data, display, 24);
|
||||||
|
|
||||||
let targetData = new Uint8Array([
|
let targetData = new Uint8Array([
|
||||||
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
|
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
|
||||||
|
|
@ -152,10 +167,12 @@ describe('Tight Decoder', function () {
|
||||||
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
|
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
expect(done).to.be.true;
|
||||||
expect(display).to.have.displayed(targetData);
|
expect(display).to.have.displayed(targetData);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle uncompressed palette rects', function () {
|
it('should handle uncompressed palette rects', function () {
|
||||||
|
let done;
|
||||||
let data1 = [
|
let data1 = [
|
||||||
// Control bytes
|
// Control bytes
|
||||||
0x40, 0x01,
|
0x40, 0x01,
|
||||||
|
|
@ -171,8 +188,10 @@ describe('Tight Decoder', function () {
|
||||||
// Pixels
|
// Pixels
|
||||||
0x01, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00 ];
|
0x01, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00 ];
|
||||||
|
|
||||||
testDecodeRect(decoder, 0, 0, 4, 2, data1, display, 24);
|
done = testDecodeRect(decoder, 0, 0, 4, 2, data1, display, 24);
|
||||||
testDecodeRect(decoder, 0, 2, 4, 2, data2, display, 24);
|
expect(done).to.be.true;
|
||||||
|
done = testDecodeRect(decoder, 0, 2, 4, 2, data2, display, 24);
|
||||||
|
expect(done).to.be.true;
|
||||||
|
|
||||||
let targetData = new Uint8Array([
|
let targetData = new Uint8Array([
|
||||||
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
|
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
|
||||||
|
|
@ -196,7 +215,7 @@ describe('Tight Decoder', function () {
|
||||||
0x62, 0x08, 0xc9, 0xc0, 0x00, 0x00, 0x00, 0x54,
|
0x62, 0x08, 0xc9, 0xc0, 0x00, 0x00, 0x00, 0x54,
|
||||||
0x00, 0x09 ];
|
0x00, 0x09 ];
|
||||||
|
|
||||||
testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
|
let done = testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
|
||||||
|
|
||||||
let targetData = new Uint8Array([
|
let targetData = new Uint8Array([
|
||||||
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
|
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
|
||||||
|
|
@ -205,6 +224,7 @@ describe('Tight Decoder', function () {
|
||||||
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
|
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
expect(done).to.be.true;
|
||||||
expect(display).to.have.displayed(targetData);
|
expect(display).to.have.displayed(targetData);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -221,7 +241,7 @@ describe('Tight Decoder', function () {
|
||||||
display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]);
|
display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]);
|
||||||
display.fillRect(0, 2, 2, 2, [ 0x00, 0xff, 0x00 ]);
|
display.fillRect(0, 2, 2, 2, [ 0x00, 0xff, 0x00 ]);
|
||||||
|
|
||||||
testDecodeRect(decoder, 1, 2, 0, 0, [ 0x00 ], display, 24);
|
let done = testDecodeRect(decoder, 1, 2, 0, 0, [ 0x00 ], display, 24);
|
||||||
|
|
||||||
let targetData = new Uint8Array([
|
let targetData = new Uint8Array([
|
||||||
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
|
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
|
||||||
|
|
@ -230,6 +250,7 @@ describe('Tight Decoder', function () {
|
||||||
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
|
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
expect(done).to.be.true;
|
||||||
expect(display).to.have.displayed(targetData);
|
expect(display).to.have.displayed(targetData);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -238,10 +259,10 @@ describe('Tight Decoder', function () {
|
||||||
display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]);
|
display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]);
|
||||||
display.fillRect(0, 2, 2, 2, [ 0x00, 0xff, 0x00 ]);
|
display.fillRect(0, 2, 2, 2, [ 0x00, 0xff, 0x00 ]);
|
||||||
|
|
||||||
testDecodeRect(decoder, 1, 2, 0, 0,
|
let done = testDecodeRect(decoder, 1, 2, 0, 0,
|
||||||
[ 0x40, 0x01, 0x01,
|
[ 0x40, 0x01, 0x01,
|
||||||
0xff, 0xff, 0xff,
|
0xff, 0xff, 0xff,
|
||||||
0xff, 0xff, 0xff ], display, 24);
|
0xff, 0xff, 0xff ], display, 24);
|
||||||
|
|
||||||
let targetData = new Uint8Array([
|
let targetData = new Uint8Array([
|
||||||
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
|
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
|
||||||
|
|
@ -250,6 +271,7 @@ describe('Tight Decoder', function () {
|
||||||
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
|
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
expect(done).to.be.true;
|
||||||
expect(display).to.have.displayed(targetData);
|
expect(display).to.have.displayed(targetData);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -258,8 +280,9 @@ describe('Tight Decoder', function () {
|
||||||
display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]);
|
display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]);
|
||||||
display.fillRect(0, 2, 2, 2, [ 0x00, 0xff, 0x00 ]);
|
display.fillRect(0, 2, 2, 2, [ 0x00, 0xff, 0x00 ]);
|
||||||
|
|
||||||
testDecodeRect(decoder, 1, 2, 0, 0,
|
let done = testDecodeRect(decoder, 1, 2, 0, 0,
|
||||||
[ 0x80, 0xff, 0xff, 0xff ], display, 24);
|
[ 0x80, 0xff, 0xff, 0xff ],
|
||||||
|
display, 24);
|
||||||
|
|
||||||
let targetData = new Uint8Array([
|
let targetData = new Uint8Array([
|
||||||
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
|
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
|
||||||
|
|
@ -268,10 +291,11 @@ describe('Tight Decoder', function () {
|
||||||
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
|
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
expect(done).to.be.true;
|
||||||
expect(display).to.have.displayed(targetData);
|
expect(display).to.have.displayed(targetData);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle JPEG rects', function (done) {
|
it('should handle JPEG rects', async function () {
|
||||||
let data = [
|
let data = [
|
||||||
// Control bytes
|
// Control bytes
|
||||||
0x90, 0xd6, 0x05,
|
0x90, 0xd6, 0x05,
|
||||||
|
|
@ -369,7 +393,8 @@ describe('Tight Decoder', function () {
|
||||||
0x3f, 0xeb, 0xff, 0x00, 0xff, 0xd9,
|
0x3f, 0xeb, 0xff, 0x00, 0xff, 0xd9,
|
||||||
];
|
];
|
||||||
|
|
||||||
testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
|
let decodeDone = testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
|
||||||
|
expect(decodeDone).to.be.true;
|
||||||
|
|
||||||
let targetData = new Uint8Array([
|
let targetData = new Uint8Array([
|
||||||
0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
|
0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
|
||||||
|
|
@ -385,10 +410,7 @@ describe('Tight Decoder', function () {
|
||||||
return diff < 5;
|
return diff < 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
display.onflush = () => {
|
await display.flush();
|
||||||
expect(display).to.have.displayed(targetData, almost);
|
expect(display).to.have.displayed(targetData, almost);
|
||||||
done();
|
|
||||||
};
|
|
||||||
display.flush();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -9,23 +9,26 @@ import FakeWebSocket from './fake.websocket.js';
|
||||||
|
|
||||||
function testDecodeRect(decoder, x, y, width, height, data, display, depth) {
|
function testDecodeRect(decoder, x, y, width, height, data, display, depth) {
|
||||||
let sock;
|
let sock;
|
||||||
|
let done = false;
|
||||||
|
|
||||||
sock = new Websock;
|
sock = new Websock;
|
||||||
sock.open("ws://example.com");
|
sock.open("ws://example.com");
|
||||||
|
|
||||||
sock.on('message', () => {
|
sock.on('message', () => {
|
||||||
decoder.decodeRect(x, y, width, height, sock, display, depth);
|
done = decoder.decodeRect(x, y, width, height, sock, display, depth);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Empty messages are filtered at multiple layers, so we need to
|
// Empty messages are filtered at multiple layers, so we need to
|
||||||
// do a direct call
|
// do a direct call
|
||||||
if (data.length === 0) {
|
if (data.length === 0) {
|
||||||
decoder.decodeRect(x, y, width, height, sock, display, depth);
|
done = decoder.decodeRect(x, y, width, height, sock, display, depth);
|
||||||
} else {
|
} else {
|
||||||
sock._websocket._receiveData(new Uint8Array(data));
|
sock._websocket._receiveData(new Uint8Array(data));
|
||||||
}
|
}
|
||||||
|
|
||||||
display.flip();
|
display.flip();
|
||||||
|
|
||||||
|
return done;
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('TightPng Decoder', function () {
|
describe('TightPng Decoder', function () {
|
||||||
|
|
@ -41,7 +44,7 @@ describe('TightPng Decoder', function () {
|
||||||
display.resize(4, 4);
|
display.resize(4, 4);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle the TightPng encoding', function (done) {
|
it('should handle the TightPng encoding', async function () {
|
||||||
let data = [
|
let data = [
|
||||||
// Control bytes
|
// Control bytes
|
||||||
0xa0, 0xb4, 0x04,
|
0xa0, 0xb4, 0x04,
|
||||||
|
|
@ -119,7 +122,8 @@ describe('TightPng Decoder', function () {
|
||||||
0xae, 0x42, 0x60, 0x82,
|
0xae, 0x42, 0x60, 0x82,
|
||||||
];
|
];
|
||||||
|
|
||||||
testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
|
let decodeDone = testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);
|
||||||
|
expect(decodeDone).to.be.true;
|
||||||
|
|
||||||
let targetData = new Uint8Array([
|
let targetData = new Uint8Array([
|
||||||
0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
|
0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
|
||||||
|
|
@ -135,10 +139,7 @@ describe('TightPng Decoder', function () {
|
||||||
return diff < 30;
|
return diff < 30;
|
||||||
}
|
}
|
||||||
|
|
||||||
display.onflush = () => {
|
await display.flush();
|
||||||
expect(display).to.have.displayed(targetData, almost);
|
expect(display).to.have.displayed(targetData, almost);
|
||||||
done();
|
|
||||||
};
|
|
||||||
display.flush();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -6,92 +6,64 @@ import FakeWebSocket from './fake.websocket.js';
|
||||||
describe('Websock', function () {
|
describe('Websock', function () {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
describe('Queue methods', function () {
|
describe('Receive queue methods', function () {
|
||||||
let sock;
|
let sock, websock;
|
||||||
const RQ_TEMPLATE = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7]);
|
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
sock = new Websock();
|
sock = new Websock();
|
||||||
// skip init
|
websock = new FakeWebSocket();
|
||||||
sock._allocateBuffers();
|
websock._open();
|
||||||
sock._rQ.set(RQ_TEMPLATE);
|
sock.attach(websock);
|
||||||
sock._rQlen = RQ_TEMPLATE.length;
|
|
||||||
});
|
|
||||||
describe('rQlen', function () {
|
|
||||||
it('should return the length of the receive queue', function () {
|
|
||||||
sock.rQi = 0;
|
|
||||||
|
|
||||||
expect(sock.rQlen).to.equal(RQ_TEMPLATE.length);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return the proper length if we read some from the receive queue", function () {
|
|
||||||
sock.rQi = 1;
|
|
||||||
|
|
||||||
expect(sock.rQlen).to.equal(RQ_TEMPLATE.length - 1);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('rQpeek8', function () {
|
describe('rQpeek8', function () {
|
||||||
it('should peek at the next byte without poping it off the queue', function () {
|
it('should peek at the next byte without poping it off the queue', function () {
|
||||||
const befLen = sock.rQlen;
|
websock._receiveData(new Uint8Array([0xab, 0xcd]));
|
||||||
const peek = sock.rQpeek8();
|
expect(sock.rQpeek8()).to.equal(0xab);
|
||||||
expect(sock.rQpeek8()).to.equal(peek);
|
expect(sock.rQpeek8()).to.equal(0xab);
|
||||||
expect(sock.rQlen).to.equal(befLen);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('rQshift8()', function () {
|
describe('rQshift8()', function () {
|
||||||
it('should pop a single byte from the receive queue', function () {
|
it('should pop a single byte from the receive queue', function () {
|
||||||
const peek = sock.rQpeek8();
|
websock._receiveData(new Uint8Array([0xab, 0xcd]));
|
||||||
const befLen = sock.rQlen;
|
expect(sock.rQshift8()).to.equal(0xab);
|
||||||
expect(sock.rQshift8()).to.equal(peek);
|
expect(sock.rQshift8()).to.equal(0xcd);
|
||||||
expect(sock.rQlen).to.equal(befLen - 1);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('rQshift16()', function () {
|
describe('rQshift16()', function () {
|
||||||
it('should pop two bytes from the receive queue and return a single number', function () {
|
it('should pop two bytes from the receive queue and return a single number', function () {
|
||||||
const befLen = sock.rQlen;
|
websock._receiveData(new Uint8Array([0xab, 0xcd, 0x12, 0x34]));
|
||||||
const expected = (RQ_TEMPLATE[0] << 8) + RQ_TEMPLATE[1];
|
expect(sock.rQshift16()).to.equal(0xabcd);
|
||||||
expect(sock.rQshift16()).to.equal(expected);
|
expect(sock.rQshift16()).to.equal(0x1234);
|
||||||
expect(sock.rQlen).to.equal(befLen - 2);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('rQshift32()', function () {
|
describe('rQshift32()', function () {
|
||||||
it('should pop four bytes from the receive queue and return a single number', function () {
|
it('should pop four bytes from the receive queue and return a single number', function () {
|
||||||
const befLen = sock.rQlen;
|
websock._receiveData(new Uint8Array([0xab, 0xcd, 0x12, 0x34,
|
||||||
const expected = (RQ_TEMPLATE[0] << 24) +
|
0x88, 0xee, 0x11, 0x33]));
|
||||||
(RQ_TEMPLATE[1] << 16) +
|
expect(sock.rQshift32()).to.equal(0xabcd1234);
|
||||||
(RQ_TEMPLATE[2] << 8) +
|
expect(sock.rQshift32()).to.equal(0x88ee1133);
|
||||||
RQ_TEMPLATE[3];
|
|
||||||
expect(sock.rQshift32()).to.equal(expected);
|
|
||||||
expect(sock.rQlen).to.equal(befLen - 4);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('rQshiftStr', function () {
|
describe('rQshiftStr', function () {
|
||||||
it('should shift the given number of bytes off of the receive queue and return a string', function () {
|
it('should shift the given number of bytes off of the receive queue and return a string', function () {
|
||||||
const befLen = sock.rQlen;
|
websock._receiveData(new Uint8Array([0xab, 0xcd, 0x12, 0x34,
|
||||||
const befRQi = sock.rQi;
|
0x88, 0xee, 0x11, 0x33]));
|
||||||
const shifted = sock.rQshiftStr(3);
|
expect(sock.rQshiftStr(4)).to.equal('\xab\xcd\x12\x34');
|
||||||
expect(shifted).to.be.a('string');
|
expect(sock.rQshiftStr(4)).to.equal('\x88\xee\x11\x33');
|
||||||
expect(shifted).to.equal(String.fromCharCode.apply(null, Array.prototype.slice.call(new Uint8Array(RQ_TEMPLATE.buffer, befRQi, 3))));
|
|
||||||
expect(sock.rQlen).to.equal(befLen - 3);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should shift the entire rest of the queue off if no length is given', function () {
|
|
||||||
sock.rQshiftStr();
|
|
||||||
expect(sock.rQlen).to.equal(0);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be able to handle very large strings', function () {
|
it('should be able to handle very large strings', function () {
|
||||||
const BIG_LEN = 500000;
|
const BIG_LEN = 500000;
|
||||||
const RQ_BIG = new Uint8Array(BIG_LEN);
|
const incoming = new Uint8Array(BIG_LEN);
|
||||||
let expected = "";
|
let expected = "";
|
||||||
let letterCode = 'a'.charCodeAt(0);
|
let letterCode = 'a'.charCodeAt(0);
|
||||||
for (let i = 0; i < BIG_LEN; i++) {
|
for (let i = 0; i < BIG_LEN; i++) {
|
||||||
RQ_BIG[i] = letterCode;
|
incoming[i] = letterCode;
|
||||||
expected += String.fromCharCode(letterCode);
|
expected += String.fromCharCode(letterCode);
|
||||||
|
|
||||||
if (letterCode < 'z'.charCodeAt(0)) {
|
if (letterCode < 'z'.charCodeAt(0)) {
|
||||||
|
|
@ -100,146 +72,282 @@ describe('Websock', function () {
|
||||||
letterCode = 'a'.charCodeAt(0);
|
letterCode = 'a'.charCodeAt(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sock._rQ.set(RQ_BIG);
|
websock._receiveData(incoming);
|
||||||
sock._rQlen = RQ_BIG.length;
|
|
||||||
|
|
||||||
const shifted = sock.rQshiftStr();
|
const shifted = sock.rQshiftStr(BIG_LEN);
|
||||||
|
|
||||||
expect(shifted).to.be.equal(expected);
|
expect(shifted).to.be.equal(expected);
|
||||||
expect(sock.rQlen).to.equal(0);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('rQshiftBytes', function () {
|
describe('rQshiftBytes', function () {
|
||||||
it('should shift the given number of bytes of the receive queue and return an array', function () {
|
it('should shift the given number of bytes of the receive queue and return an array', function () {
|
||||||
const befLen = sock.rQlen;
|
websock._receiveData(new Uint8Array([0xab, 0xcd, 0x12, 0x34,
|
||||||
const befRQi = sock.rQi;
|
0x88, 0xee, 0x11, 0x33]));
|
||||||
const shifted = sock.rQshiftBytes(3);
|
expect(sock.rQshiftBytes(4)).to.array.equal(new Uint8Array([0xab, 0xcd, 0x12, 0x34]));
|
||||||
expect(shifted).to.be.an.instanceof(Uint8Array);
|
expect(sock.rQshiftBytes(4)).to.array.equal(new Uint8Array([0x88, 0xee, 0x11, 0x33]));
|
||||||
expect(shifted).to.array.equal(new Uint8Array(RQ_TEMPLATE.buffer, befRQi, 3));
|
|
||||||
expect(sock.rQlen).to.equal(befLen - 3);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should shift the entire rest of the queue off if no length is given', function () {
|
it('should return a shared array if requested', function () {
|
||||||
sock.rQshiftBytes();
|
websock._receiveData(new Uint8Array([0xab, 0xcd, 0x12, 0x34,
|
||||||
expect(sock.rQlen).to.equal(0);
|
0x88, 0xee, 0x11, 0x33]));
|
||||||
|
const bytes = sock.rQshiftBytes(4, false);
|
||||||
|
expect(bytes).to.array.equal(new Uint8Array([0xab, 0xcd, 0x12, 0x34]));
|
||||||
|
expect(bytes.buffer.byteLength).to.not.equal(bytes.length);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('rQslice', function () {
|
describe('rQpeekBytes', function () {
|
||||||
beforeEach(function () {
|
|
||||||
sock.rQi = 0;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not modify the receive queue', function () {
|
it('should not modify the receive queue', function () {
|
||||||
const befLen = sock.rQlen;
|
websock._receiveData(new Uint8Array([0xab, 0xcd, 0x12, 0x34,
|
||||||
sock.rQslice(0, 2);
|
0x88, 0xee, 0x11, 0x33]));
|
||||||
expect(sock.rQlen).to.equal(befLen);
|
expect(sock.rQpeekBytes(4)).to.array.equal(new Uint8Array([0xab, 0xcd, 0x12, 0x34]));
|
||||||
|
expect(sock.rQpeekBytes(4)).to.array.equal(new Uint8Array([0xab, 0xcd, 0x12, 0x34]));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return an array containing the given slice of the receive queue', function () {
|
it('should return a shared array if requested', function () {
|
||||||
const sl = sock.rQslice(0, 2);
|
websock._receiveData(new Uint8Array([0xab, 0xcd, 0x12, 0x34,
|
||||||
expect(sl).to.be.an.instanceof(Uint8Array);
|
0x88, 0xee, 0x11, 0x33]));
|
||||||
expect(sl).to.array.equal(new Uint8Array(RQ_TEMPLATE.buffer, 0, 2));
|
const bytes = sock.rQpeekBytes(4, false);
|
||||||
});
|
expect(bytes).to.array.equal(new Uint8Array([0xab, 0xcd, 0x12, 0x34]));
|
||||||
|
expect(bytes.buffer.byteLength).to.not.equal(bytes.length);
|
||||||
it('should use the rest of the receive queue if no end is given', function () {
|
|
||||||
const sl = sock.rQslice(1);
|
|
||||||
expect(sl).to.have.length(RQ_TEMPLATE.length - 1);
|
|
||||||
expect(sl).to.array.equal(new Uint8Array(RQ_TEMPLATE.buffer, 1));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should take the current rQi in to account', function () {
|
|
||||||
sock.rQi = 1;
|
|
||||||
expect(sock.rQslice(0, 2)).to.array.equal(new Uint8Array(RQ_TEMPLATE.buffer, 1, 2));
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('rQwait', function () {
|
describe('rQwait', function () {
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
sock.rQi = 0;
|
websock._receiveData(new Uint8Array([0xab, 0xcd, 0x12, 0x34,
|
||||||
|
0x88, 0xee, 0x11, 0x33]));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return true if there are not enough bytes in the receive queue', function () {
|
it('should return true if there are not enough bytes in the receive queue', function () {
|
||||||
expect(sock.rQwait('hi', RQ_TEMPLATE.length + 1)).to.be.true;
|
expect(sock.rQwait('hi', 9)).to.be.true;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return false if there are enough bytes in the receive queue', function () {
|
it('should return false if there are enough bytes in the receive queue', function () {
|
||||||
expect(sock.rQwait('hi', RQ_TEMPLATE.length)).to.be.false;
|
expect(sock.rQwait('hi', 8)).to.be.false;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return true and reduce rQi by "goback" if there are not enough bytes', function () {
|
it('should return true and reduce rQi by "goback" if there are not enough bytes', function () {
|
||||||
sock.rQi = 5;
|
expect(sock.rQshift32()).to.equal(0xabcd1234);
|
||||||
expect(sock.rQwait('hi', RQ_TEMPLATE.length, 4)).to.be.true;
|
expect(sock.rQwait('hi', 8, 2)).to.be.true;
|
||||||
expect(sock.rQi).to.equal(1);
|
expect(sock.rQshift32()).to.equal(0x123488ee);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should raise an error if we try to go back more than possible', function () {
|
it('should raise an error if we try to go back more than possible', function () {
|
||||||
sock.rQi = 5;
|
expect(sock.rQshift32()).to.equal(0xabcd1234);
|
||||||
expect(() => sock.rQwait('hi', RQ_TEMPLATE.length, 6)).to.throw(Error);
|
expect(() => sock.rQwait('hi', 8, 6)).to.throw(Error);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not reduce rQi if there are enough bytes', function () {
|
it('should not reduce rQi if there are enough bytes', function () {
|
||||||
sock.rQi = 5;
|
expect(sock.rQshift32()).to.equal(0xabcd1234);
|
||||||
sock.rQwait('hi', 1, 6);
|
expect(sock.rQwait('hi', 4, 2)).to.be.false;
|
||||||
expect(sock.rQi).to.equal(5);
|
expect(sock.rQshift32()).to.equal(0x88ee1133);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Send queue methods', function () {
|
||||||
|
let sock;
|
||||||
|
|
||||||
|
const bufferSize = 10 * 1024;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
let websock = new FakeWebSocket();
|
||||||
|
websock._open();
|
||||||
|
sock = new Websock();
|
||||||
|
sock.attach(websock);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('sQpush8()', function () {
|
||||||
|
it('should send a single byte', function () {
|
||||||
|
sock.sQpush8(42);
|
||||||
|
sock.flush();
|
||||||
|
expect(sock).to.have.sent(new Uint8Array([42]));
|
||||||
|
});
|
||||||
|
it('should not send any data until flushing', function () {
|
||||||
|
sock.sQpush8(42);
|
||||||
|
expect(sock).to.have.sent(new Uint8Array([]));
|
||||||
|
});
|
||||||
|
it('should implicitly flush if the queue is full', function () {
|
||||||
|
for (let i = 0;i <= bufferSize;i++) {
|
||||||
|
sock.sQpush8(42);
|
||||||
|
}
|
||||||
|
|
||||||
|
let expected = [];
|
||||||
|
for (let i = 0;i < bufferSize;i++) {
|
||||||
|
expected.push(42);
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(sock).to.have.sent(new Uint8Array(expected));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('sQpush16()', function () {
|
||||||
|
it('should send a number as two bytes', function () {
|
||||||
|
sock.sQpush16(420);
|
||||||
|
sock.flush();
|
||||||
|
expect(sock).to.have.sent(new Uint8Array([1, 164]));
|
||||||
|
});
|
||||||
|
it('should not send any data until flushing', function () {
|
||||||
|
sock.sQpush16(420);
|
||||||
|
expect(sock).to.have.sent(new Uint8Array([]));
|
||||||
|
});
|
||||||
|
it('should implicitly flush if the queue is full', function () {
|
||||||
|
for (let i = 0;i <= bufferSize/2;i++) {
|
||||||
|
sock.sQpush16(420);
|
||||||
|
}
|
||||||
|
|
||||||
|
let expected = [];
|
||||||
|
for (let i = 0;i < bufferSize/2;i++) {
|
||||||
|
expected.push(1);
|
||||||
|
expected.push(164);
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(sock).to.have.sent(new Uint8Array(expected));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('sQpush32()', function () {
|
||||||
|
it('should send a number as two bytes', function () {
|
||||||
|
sock.sQpush32(420420);
|
||||||
|
sock.flush();
|
||||||
|
expect(sock).to.have.sent(new Uint8Array([0, 6, 106, 68]));
|
||||||
|
});
|
||||||
|
it('should not send any data until flushing', function () {
|
||||||
|
sock.sQpush32(420420);
|
||||||
|
expect(sock).to.have.sent(new Uint8Array([]));
|
||||||
|
});
|
||||||
|
it('should implicitly flush if the queue is full', function () {
|
||||||
|
for (let i = 0;i <= bufferSize/4;i++) {
|
||||||
|
sock.sQpush32(420420);
|
||||||
|
}
|
||||||
|
|
||||||
|
let expected = [];
|
||||||
|
for (let i = 0;i < bufferSize/4;i++) {
|
||||||
|
expected.push(0);
|
||||||
|
expected.push(6);
|
||||||
|
expected.push(106);
|
||||||
|
expected.push(68);
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(sock).to.have.sent(new Uint8Array(expected));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('sQpushString()', function () {
|
||||||
|
it('should send a string buffer', function () {
|
||||||
|
sock.sQpushString('\x12\x34\x56\x78\x90');
|
||||||
|
sock.flush();
|
||||||
|
expect(sock).to.have.sent(new Uint8Array([0x12, 0x34, 0x56, 0x78, 0x90]));
|
||||||
|
});
|
||||||
|
it('should not send any data until flushing', function () {
|
||||||
|
sock.sQpushString('\x12\x34\x56\x78\x90');
|
||||||
|
expect(sock).to.have.sent(new Uint8Array([]));
|
||||||
|
});
|
||||||
|
it('should implicitly flush if the queue is full', function () {
|
||||||
|
for (let i = 0;i <= bufferSize/5;i++) {
|
||||||
|
sock.sQpushString('\x12\x34\x56\x78\x90');
|
||||||
|
}
|
||||||
|
|
||||||
|
let expected = [];
|
||||||
|
for (let i = 0;i < bufferSize/5;i++) {
|
||||||
|
expected.push(0x12);
|
||||||
|
expected.push(0x34);
|
||||||
|
expected.push(0x56);
|
||||||
|
expected.push(0x78);
|
||||||
|
expected.push(0x90);
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(sock).to.have.sent(new Uint8Array(expected));
|
||||||
|
});
|
||||||
|
it('should implicitly split a large buffer', function () {
|
||||||
|
let str = '';
|
||||||
|
for (let i = 0;i <= bufferSize/5;i++) {
|
||||||
|
str += '\x12\x34\x56\x78\x90';
|
||||||
|
}
|
||||||
|
|
||||||
|
sock.sQpushString(str);
|
||||||
|
|
||||||
|
let expected = [];
|
||||||
|
for (let i = 0;i < bufferSize/5;i++) {
|
||||||
|
expected.push(0x12);
|
||||||
|
expected.push(0x34);
|
||||||
|
expected.push(0x56);
|
||||||
|
expected.push(0x78);
|
||||||
|
expected.push(0x90);
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(sock).to.have.sent(new Uint8Array(expected));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('sQpushBytes()', function () {
|
||||||
|
it('should send a byte buffer', function () {
|
||||||
|
sock.sQpushBytes(new Uint8Array([0x12, 0x34, 0x56, 0x78, 0x90]));
|
||||||
|
sock.flush();
|
||||||
|
expect(sock).to.have.sent(new Uint8Array([0x12, 0x34, 0x56, 0x78, 0x90]));
|
||||||
|
});
|
||||||
|
it('should not send any data until flushing', function () {
|
||||||
|
sock.sQpushBytes(new Uint8Array([0x12, 0x34, 0x56, 0x78, 0x90]));
|
||||||
|
expect(sock).to.have.sent(new Uint8Array([]));
|
||||||
|
});
|
||||||
|
it('should implicitly flush if the queue is full', function () {
|
||||||
|
for (let i = 0;i <= bufferSize/5;i++) {
|
||||||
|
sock.sQpushBytes(new Uint8Array([0x12, 0x34, 0x56, 0x78, 0x90]));
|
||||||
|
}
|
||||||
|
|
||||||
|
let expected = [];
|
||||||
|
for (let i = 0;i < bufferSize/5;i++) {
|
||||||
|
expected.push(0x12);
|
||||||
|
expected.push(0x34);
|
||||||
|
expected.push(0x56);
|
||||||
|
expected.push(0x78);
|
||||||
|
expected.push(0x90);
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(sock).to.have.sent(new Uint8Array(expected));
|
||||||
|
});
|
||||||
|
it('should implicitly split a large buffer', function () {
|
||||||
|
let buffer = [];
|
||||||
|
for (let i = 0;i <= bufferSize/5;i++) {
|
||||||
|
buffer.push(0x12);
|
||||||
|
buffer.push(0x34);
|
||||||
|
buffer.push(0x56);
|
||||||
|
buffer.push(0x78);
|
||||||
|
buffer.push(0x90);
|
||||||
|
}
|
||||||
|
|
||||||
|
sock.sQpushBytes(new Uint8Array(buffer));
|
||||||
|
|
||||||
|
let expected = [];
|
||||||
|
for (let i = 0;i < bufferSize/5;i++) {
|
||||||
|
expected.push(0x12);
|
||||||
|
expected.push(0x34);
|
||||||
|
expected.push(0x56);
|
||||||
|
expected.push(0x78);
|
||||||
|
expected.push(0x90);
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(sock).to.have.sent(new Uint8Array(expected));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('flush', function () {
|
describe('flush', function () {
|
||||||
beforeEach(function () {
|
|
||||||
sock._websocket = {
|
|
||||||
send: sinon.spy()
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should actually send on the websocket', function () {
|
it('should actually send on the websocket', function () {
|
||||||
sock._websocket.bufferedAmount = 8;
|
|
||||||
sock._websocket.readyState = WebSocket.OPEN;
|
|
||||||
sock._sQ = new Uint8Array([1, 2, 3]);
|
sock._sQ = new Uint8Array([1, 2, 3]);
|
||||||
sock._sQlen = 3;
|
sock._sQlen = 3;
|
||||||
const encoded = sock._encodeMessage();
|
|
||||||
|
|
||||||
sock.flush();
|
sock.flush();
|
||||||
expect(sock._websocket.send).to.have.been.calledOnce;
|
expect(sock).to.have.sent(new Uint8Array([1, 2, 3]));
|
||||||
expect(sock._websocket.send).to.have.been.calledWith(encoded);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not call send if we do not have anything queued up', function () {
|
it('should not call send if we do not have anything queued up', function () {
|
||||||
sock._sQlen = 0;
|
sock._sQlen = 0;
|
||||||
sock._websocket.bufferedAmount = 8;
|
|
||||||
|
|
||||||
sock.flush();
|
sock.flush();
|
||||||
|
|
||||||
expect(sock._websocket.send).not.to.have.been.called;
|
expect(sock).to.have.sent(new Uint8Array([]));
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('send', function () {
|
|
||||||
beforeEach(function () {
|
|
||||||
sock.flush = sinon.spy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should add to the send queue', function () {
|
|
||||||
sock.send([1, 2, 3]);
|
|
||||||
const sq = sock.sQ;
|
|
||||||
expect(new Uint8Array(sq.buffer, sock._sQlen - 3, 3)).to.array.equal(new Uint8Array([1, 2, 3]));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should call flush', function () {
|
|
||||||
sock.send([1, 2, 3]);
|
|
||||||
expect(sock.flush).to.have.been.calledOnce;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('sendString', function () {
|
|
||||||
beforeEach(function () {
|
|
||||||
sock.send = sinon.spy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should call send after converting the string to an array', function () {
|
|
||||||
sock.sendString("\x01\x02\x03");
|
|
||||||
expect(sock.send).to.have.been.calledWith([1, 2, 3]);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@ -450,9 +558,8 @@ describe('Websock', function () {
|
||||||
sock._allocateBuffers();
|
sock._allocateBuffers();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should support adding binary Uint8Array data to the receive queue', function () {
|
it('should support adding data to the receive queue', function () {
|
||||||
const msg = { data: new Uint8Array([1, 2, 3]) };
|
const msg = { data: new Uint8Array([1, 2, 3]) };
|
||||||
sock._mode = 'binary';
|
|
||||||
sock._recvMessage(msg);
|
sock._recvMessage(msg);
|
||||||
expect(sock.rQshiftStr(3)).to.equal('\x01\x02\x03');
|
expect(sock.rQshiftStr(3)).to.equal('\x01\x02\x03');
|
||||||
});
|
});
|
||||||
|
|
@ -473,80 +580,49 @@ describe('Websock', function () {
|
||||||
expect(sock._eventHandlers.message).not.to.have.been.called;
|
expect(sock._eventHandlers.message).not.to.have.been.called;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should compact the receive queue when a message handler empties it', function () {
|
it('should compact the receive queue when fully read', function () {
|
||||||
sock._eventHandlers.message = () => { sock.rQi = sock._rQlen; };
|
|
||||||
sock._rQ = new Uint8Array([0, 1, 2, 3, 4, 5, 0, 0, 0, 0]);
|
sock._rQ = new Uint8Array([0, 1, 2, 3, 4, 5, 0, 0, 0, 0]);
|
||||||
sock._rQlen = 6;
|
sock._rQlen = 6;
|
||||||
sock.rQi = 6;
|
sock._rQi = 6;
|
||||||
const msg = { data: new Uint8Array([1, 2, 3]).buffer };
|
const msg = { data: new Uint8Array([1, 2, 3]).buffer };
|
||||||
sock._mode = 'binary';
|
|
||||||
sock._recvMessage(msg);
|
sock._recvMessage(msg);
|
||||||
expect(sock._rQlen).to.equal(0);
|
expect(sock._rQlen).to.equal(3);
|
||||||
expect(sock.rQi).to.equal(0);
|
expect(sock._rQi).to.equal(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should compact the receive queue when we reach the end of the buffer', function () {
|
it('should compact the receive queue when we reach the end of the buffer', function () {
|
||||||
sock._rQ = new Uint8Array(20);
|
sock._rQ = new Uint8Array(20);
|
||||||
sock._rQbufferSize = 20;
|
sock._rQbufferSize = 20;
|
||||||
sock._rQlen = 20;
|
sock._rQlen = 20;
|
||||||
sock.rQi = 10;
|
sock._rQi = 10;
|
||||||
const msg = { data: new Uint8Array([1, 2]).buffer };
|
const msg = { data: new Uint8Array([1, 2]).buffer };
|
||||||
sock._mode = 'binary';
|
|
||||||
sock._recvMessage(msg);
|
sock._recvMessage(msg);
|
||||||
expect(sock._rQlen).to.equal(12);
|
expect(sock._rQlen).to.equal(12);
|
||||||
expect(sock.rQi).to.equal(0);
|
expect(sock._rQi).to.equal(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should automatically resize the receive queue if the incoming message is larger than the buffer', function () {
|
it('should automatically resize the receive queue if the incoming message is larger than the buffer', function () {
|
||||||
sock._rQ = new Uint8Array(20);
|
sock._rQ = new Uint8Array(20);
|
||||||
sock._rQlen = 0;
|
sock._rQlen = 0;
|
||||||
sock.rQi = 0;
|
sock._rQi = 0;
|
||||||
sock._rQbufferSize = 20;
|
sock._rQbufferSize = 20;
|
||||||
const msg = { data: new Uint8Array(30).buffer };
|
const msg = { data: new Uint8Array(30).buffer };
|
||||||
sock._mode = 'binary';
|
|
||||||
sock._recvMessage(msg);
|
sock._recvMessage(msg);
|
||||||
expect(sock._rQlen).to.equal(30);
|
expect(sock._rQlen).to.equal(30);
|
||||||
expect(sock.rQi).to.equal(0);
|
expect(sock._rQi).to.equal(0);
|
||||||
expect(sock._rQ.length).to.equal(240); // keep the invariant that rQbufferSize / 8 >= rQlen
|
expect(sock._rQ.length).to.equal(240); // keep the invariant that rQbufferSize / 8 >= rQlen
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should automatically resize the receive queue if the incoming message is larger than 1/8th of the buffer and we reach the end of the buffer', function () {
|
it('should automatically resize the receive queue if the incoming message is larger than 1/8th of the buffer and we reach the end of the buffer', function () {
|
||||||
sock._rQ = new Uint8Array(20);
|
sock._rQ = new Uint8Array(20);
|
||||||
sock._rQlen = 16;
|
sock._rQlen = 16;
|
||||||
sock.rQi = 16;
|
sock._rQi = 15;
|
||||||
sock._rQbufferSize = 20;
|
sock._rQbufferSize = 20;
|
||||||
const msg = { data: new Uint8Array(6).buffer };
|
const msg = { data: new Uint8Array(6).buffer };
|
||||||
sock._mode = 'binary';
|
|
||||||
sock._recvMessage(msg);
|
sock._recvMessage(msg);
|
||||||
expect(sock._rQlen).to.equal(6);
|
expect(sock._rQlen).to.equal(7);
|
||||||
expect(sock.rQi).to.equal(0);
|
expect(sock._rQi).to.equal(0);
|
||||||
expect(sock._rQ.length).to.equal(48);
|
expect(sock._rQ.length).to.equal(56);
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Data encoding', function () {
|
|
||||||
before(function () { FakeWebSocket.replace(); });
|
|
||||||
after(function () { FakeWebSocket.restore(); });
|
|
||||||
|
|
||||||
describe('as binary data', function () {
|
|
||||||
let sock;
|
|
||||||
beforeEach(function () {
|
|
||||||
sock = new Websock();
|
|
||||||
sock.open('ws://', 'binary');
|
|
||||||
sock._websocket._open();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should only send the send queue up to the send queue length', function () {
|
|
||||||
sock._sQ = new Uint8Array([1, 2, 3, 4, 5]);
|
|
||||||
sock._sQlen = 3;
|
|
||||||
const res = sock._encodeMessage();
|
|
||||||
expect(res).to.array.equal(new Uint8Array([1, 2, 3]));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should properly pass the encoded data off to the actual WebSocket', function () {
|
|
||||||
sock.send([1, 2, 3]);
|
|
||||||
expect(sock._websocket._getSentData()).to.array.equal(new Uint8Array([1, 2, 3]));
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -8,39 +8,48 @@ describe('WebUtil', function () {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
describe('config variables', function () {
|
describe('config variables', function () {
|
||||||
|
let origState, origHref;
|
||||||
|
beforeEach(function () {
|
||||||
|
origState = history.state;
|
||||||
|
origHref = location.href;
|
||||||
|
});
|
||||||
|
afterEach(function () {
|
||||||
|
history.replaceState(origState, '', origHref);
|
||||||
|
});
|
||||||
|
|
||||||
it('should parse query string variables', function () {
|
it('should parse query string variables', function () {
|
||||||
// history.pushState() will not cause the browser to attempt loading
|
// history.pushState() will not cause the browser to attempt loading
|
||||||
// the URL, this is exactly what we want here for the tests.
|
// the URL, this is exactly what we want here for the tests.
|
||||||
history.pushState({}, '', "test?myvar=myval");
|
history.replaceState({}, '', "test?myvar=myval");
|
||||||
expect(WebUtil.getConfigVar("myvar")).to.be.equal("myval");
|
expect(WebUtil.getConfigVar("myvar")).to.be.equal("myval");
|
||||||
});
|
});
|
||||||
it('should return default value when no query match', function () {
|
it('should return default value when no query match', function () {
|
||||||
history.pushState({}, '', "test?myvar=myval");
|
history.replaceState({}, '', "test?myvar=myval");
|
||||||
expect(WebUtil.getConfigVar("other", "def")).to.be.equal("def");
|
expect(WebUtil.getConfigVar("other", "def")).to.be.equal("def");
|
||||||
});
|
});
|
||||||
it('should handle no query match and no default value', function () {
|
it('should handle no query match and no default value', function () {
|
||||||
history.pushState({}, '', "test?myvar=myval");
|
history.replaceState({}, '', "test?myvar=myval");
|
||||||
expect(WebUtil.getConfigVar("other")).to.be.equal(null);
|
expect(WebUtil.getConfigVar("other")).to.be.equal(null);
|
||||||
});
|
});
|
||||||
it('should parse fragment variables', function () {
|
it('should parse fragment variables', function () {
|
||||||
history.pushState({}, '', "test#myvar=myval");
|
history.replaceState({}, '', "test#myvar=myval");
|
||||||
expect(WebUtil.getConfigVar("myvar")).to.be.equal("myval");
|
expect(WebUtil.getConfigVar("myvar")).to.be.equal("myval");
|
||||||
});
|
});
|
||||||
it('should return default value when no fragment match', function () {
|
it('should return default value when no fragment match', function () {
|
||||||
history.pushState({}, '', "test#myvar=myval");
|
history.replaceState({}, '', "test#myvar=myval");
|
||||||
expect(WebUtil.getConfigVar("other", "def")).to.be.equal("def");
|
expect(WebUtil.getConfigVar("other", "def")).to.be.equal("def");
|
||||||
});
|
});
|
||||||
it('should handle no fragment match and no default value', function () {
|
it('should handle no fragment match and no default value', function () {
|
||||||
history.pushState({}, '', "test#myvar=myval");
|
history.replaceState({}, '', "test#myvar=myval");
|
||||||
expect(WebUtil.getConfigVar("other")).to.be.equal(null);
|
expect(WebUtil.getConfigVar("other")).to.be.equal(null);
|
||||||
});
|
});
|
||||||
it('should handle both query and fragment', function () {
|
it('should handle both query and fragment', function () {
|
||||||
history.pushState({}, '', "test?myquery=1#myhash=2");
|
history.replaceState({}, '', "test?myquery=1#myhash=2");
|
||||||
expect(WebUtil.getConfigVar("myquery")).to.be.equal("1");
|
expect(WebUtil.getConfigVar("myquery")).to.be.equal("1");
|
||||||
expect(WebUtil.getConfigVar("myhash")).to.be.equal("2");
|
expect(WebUtil.getConfigVar("myhash")).to.be.equal("2");
|
||||||
});
|
});
|
||||||
it('should prioritize fragment if both provide same var', function () {
|
it('should prioritize fragment if both provide same var', function () {
|
||||||
history.pushState({}, '', "test?myvar=1#myvar=2");
|
history.replaceState({}, '', "test?myvar=1#myvar=2");
|
||||||
expect(WebUtil.getConfigVar("myvar")).to.be.equal("2");
|
expect(WebUtil.getConfigVar("myvar")).to.be.equal("2");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@ -83,6 +92,11 @@ describe('WebUtil', function () {
|
||||||
expect(window.localStorage.setItem).to.have.been.calledWithExactly('test', 'value');
|
expect(window.localStorage.setItem).to.have.been.calledWithExactly('test', 'value');
|
||||||
expect(WebUtil.readSetting('test')).to.equal('value');
|
expect(WebUtil.readSetting('test')).to.equal('value');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should not crash when local storage save fails', function () {
|
||||||
|
localStorage.setItem.throws(new DOMException());
|
||||||
|
expect(WebUtil.writeSetting('test', 'value')).to.not.throw;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('setSetting', function () {
|
describe('setSetting', function () {
|
||||||
|
|
@ -128,6 +142,11 @@ describe('WebUtil', function () {
|
||||||
WebUtil.writeSetting('test', 'something else');
|
WebUtil.writeSetting('test', 'something else');
|
||||||
expect(WebUtil.readSetting('test')).to.equal('something else');
|
expect(WebUtil.readSetting('test')).to.equal('something else');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should not crash when local storage read fails', function () {
|
||||||
|
localStorage.getItem.throws(new DOMException());
|
||||||
|
expect(WebUtil.readSetting('test')).to.not.throw;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// this doesn't appear to be used anywhere
|
// this doesn't appear to be used anywhere
|
||||||
|
|
@ -136,6 +155,11 @@ describe('WebUtil', function () {
|
||||||
WebUtil.eraseSetting('test');
|
WebUtil.eraseSetting('test');
|
||||||
expect(window.localStorage.removeItem).to.have.been.calledWithExactly('test');
|
expect(window.localStorage.removeItem).to.have.been.calledWithExactly('test');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should not crash when local storage remove fails', function () {
|
||||||
|
localStorage.removeItem.throws(new DOMException());
|
||||||
|
expect(WebUtil.eraseSetting('test')).to.not.throw;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,23 +9,26 @@ import FakeWebSocket from './fake.websocket.js';
|
||||||
|
|
||||||
function testDecodeRect(decoder, x, y, width, height, data, display, depth) {
|
function testDecodeRect(decoder, x, y, width, height, data, display, depth) {
|
||||||
let sock;
|
let sock;
|
||||||
|
let done = false;
|
||||||
|
|
||||||
sock = new Websock;
|
sock = new Websock;
|
||||||
sock.open("ws://example.com");
|
sock.open("ws://example.com");
|
||||||
|
|
||||||
sock.on('message', () => {
|
sock.on('message', () => {
|
||||||
decoder.decodeRect(x, y, width, height, sock, display, depth);
|
done = decoder.decodeRect(x, y, width, height, sock, display, depth);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Empty messages are filtered at multiple layers, so we need to
|
// Empty messages are filtered at multiple layers, so we need to
|
||||||
// do a direct call
|
// do a direct call
|
||||||
if (data.length === 0) {
|
if (data.length === 0) {
|
||||||
decoder.decodeRect(x, y, width, height, sock, display, depth);
|
done = decoder.decodeRect(x, y, width, height, sock, display, depth);
|
||||||
} else {
|
} else {
|
||||||
sock._websocket._receiveData(new Uint8Array(data));
|
sock._websocket._receiveData(new Uint8Array(data));
|
||||||
}
|
}
|
||||||
|
|
||||||
display.flip();
|
display.flip();
|
||||||
|
|
||||||
|
return done;
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('ZRLE Decoder', function () {
|
describe('ZRLE Decoder', function () {
|
||||||
|
|
@ -42,9 +45,11 @@ describe('ZRLE Decoder', function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle the Raw subencoding', function () {
|
it('should handle the Raw subencoding', function () {
|
||||||
testDecodeRect(decoder, 0, 0, 4, 4,
|
let done = testDecodeRect(decoder, 0, 0, 4, 4,
|
||||||
[0x00, 0x00, 0x00, 0x0e, 0x78, 0x5e, 0x62, 0x60, 0x60, 0xf8, 0x4f, 0x12, 0x02, 0x00, 0x00, 0x00, 0xff, 0xff],
|
[0x00, 0x00, 0x00, 0x0e, 0x78, 0x5e,
|
||||||
display, 24);
|
0x62, 0x60, 0x60, 0xf8, 0x4f, 0x12,
|
||||||
|
0x02, 0x00, 0x00, 0x00, 0xff, 0xff],
|
||||||
|
display, 24);
|
||||||
|
|
||||||
let targetData = new Uint8Array([
|
let targetData = new Uint8Array([
|
||||||
0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff,
|
0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff,
|
||||||
|
|
@ -53,13 +58,16 @@ describe('ZRLE Decoder', function () {
|
||||||
0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff
|
0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
expect(done).to.be.true;
|
||||||
expect(display).to.have.displayed(targetData);
|
expect(display).to.have.displayed(targetData);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle the Solid subencoding', function () {
|
it('should handle the Solid subencoding', function () {
|
||||||
testDecodeRect(decoder, 0, 0, 4, 4,
|
let done = testDecodeRect(decoder, 0, 0, 4, 4,
|
||||||
[0x00, 0x00, 0x00, 0x0c, 0x78, 0x5e, 0x62, 0x64, 0x60, 0xf8, 0x0f, 0x00, 0x00, 0x00, 0xff, 0xff],
|
[0x00, 0x00, 0x00, 0x0c, 0x78, 0x5e,
|
||||||
display, 24);
|
0x62, 0x64, 0x60, 0xf8, 0x0f, 0x00,
|
||||||
|
0x00, 0x00, 0xff, 0xff],
|
||||||
|
display, 24);
|
||||||
|
|
||||||
let targetData = new Uint8Array([
|
let targetData = new Uint8Array([
|
||||||
0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff,
|
0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff,
|
||||||
|
|
@ -68,14 +76,18 @@ describe('ZRLE Decoder', function () {
|
||||||
0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff
|
0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
expect(done).to.be.true;
|
||||||
expect(display).to.have.displayed(targetData);
|
expect(display).to.have.displayed(targetData);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it('should handle the Palette Tile subencoding', function () {
|
it('should handle the Palette Tile subencoding', function () {
|
||||||
testDecodeRect(decoder, 0, 0, 4, 4,
|
let done = testDecodeRect(decoder, 0, 0, 4, 4,
|
||||||
[0x00, 0x00, 0x00, 0x12, 0x78, 0x5E, 0x62, 0x62, 0x60, 248, 0xff, 0x9F, 0x01, 0x08, 0x3E, 0x7C, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff],
|
[0x00, 0x00, 0x00, 0x12, 0x78, 0x5E,
|
||||||
display, 24);
|
0x62, 0x62, 0x60, 248, 0xff, 0x9F,
|
||||||
|
0x01, 0x08, 0x3E, 0x7C, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0xff, 0xff],
|
||||||
|
display, 24);
|
||||||
|
|
||||||
let targetData = new Uint8Array([
|
let targetData = new Uint8Array([
|
||||||
0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff,
|
0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff,
|
||||||
|
|
@ -84,13 +96,16 @@ describe('ZRLE Decoder', function () {
|
||||||
0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff
|
0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
expect(done).to.be.true;
|
||||||
expect(display).to.have.displayed(targetData);
|
expect(display).to.have.displayed(targetData);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle the RLE Tile subencoding', function () {
|
it('should handle the RLE Tile subencoding', function () {
|
||||||
testDecodeRect(decoder, 0, 0, 4, 4,
|
let done = testDecodeRect(decoder, 0, 0, 4, 4,
|
||||||
[0x00, 0x00, 0x00, 0x0d, 0x78, 0x5e, 0x6a, 0x60, 0x60, 0xf8, 0x2f, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff],
|
[0x00, 0x00, 0x00, 0x0d, 0x78, 0x5e,
|
||||||
display, 24);
|
0x6a, 0x60, 0x60, 0xf8, 0x2f, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0xff, 0xff],
|
||||||
|
display, 24);
|
||||||
|
|
||||||
let targetData = new Uint8Array([
|
let targetData = new Uint8Array([
|
||||||
0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff,
|
0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff,
|
||||||
|
|
@ -99,13 +114,17 @@ describe('ZRLE Decoder', function () {
|
||||||
0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff
|
0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
expect(done).to.be.true;
|
||||||
expect(display).to.have.displayed(targetData);
|
expect(display).to.have.displayed(targetData);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle the RLE Palette Tile subencoding', function () {
|
it('should handle the RLE Palette Tile subencoding', function () {
|
||||||
testDecodeRect(decoder, 0, 0, 4, 4,
|
let done = testDecodeRect(decoder, 0, 0, 4, 4,
|
||||||
[0x00, 0x00, 0x00, 0x11, 0x78, 0x5e, 0x6a, 0x62, 0x60, 0xf8, 0xff, 0x9f, 0x81, 0xa1, 0x81, 0x1f, 0x00, 0x00, 0x00, 0xff, 0xff],
|
[0x00, 0x00, 0x00, 0x11, 0x78, 0x5e,
|
||||||
display, 24);
|
0x6a, 0x62, 0x60, 0xf8, 0xff, 0x9f,
|
||||||
|
0x81, 0xa1, 0x81, 0x1f, 0x00, 0x00,
|
||||||
|
0x00, 0xff, 0xff],
|
||||||
|
display, 24);
|
||||||
|
|
||||||
let targetData = new Uint8Array([
|
let targetData = new Uint8Array([
|
||||||
0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff,
|
0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff,
|
||||||
|
|
@ -114,6 +133,7 @@ describe('ZRLE Decoder', function () {
|
||||||
0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff
|
0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
expect(done).to.be.true;
|
||||||
expect(display).to.have.displayed(targetData);
|
expect(display).to.have.displayed(targetData);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -107,20 +107,13 @@
|
||||||
// query string. If the variable isn't defined in the URL
|
// query string. If the variable isn't defined in the URL
|
||||||
// it returns the default value instead.
|
// it returns the default value instead.
|
||||||
function readQueryVariable(name, defaultValue) {
|
function readQueryVariable(name, defaultValue) {
|
||||||
// A URL with a query parameter can look like this (But will most probably get logged on the http server):
|
// A URL with a query parameter can look like this:
|
||||||
// https://www.example.com?myqueryparam=myvalue
|
// https://www.example.com?myqueryparam=myvalue
|
||||||
//
|
//
|
||||||
// For privacy (Using a hastag #, the parameters will not be sent to the server)
|
|
||||||
// the url can be requested in the following way:
|
|
||||||
// https://www.example.com#myqueryparam=myvalue&password=secreatvalue
|
|
||||||
//
|
|
||||||
// Even Mixing public and non public parameters will work:
|
|
||||||
// https://www.example.com?nonsecretparam=example.com#password=secreatvalue
|
|
||||||
//
|
|
||||||
// Note that we use location.href instead of location.search
|
// Note that we use location.href instead of location.search
|
||||||
// because Firefox < 53 has a bug w.r.t location.search
|
// because Firefox < 53 has a bug w.r.t location.search
|
||||||
const re = new RegExp('.*[?&]' + name + '=([^&#]*)'),
|
const re = new RegExp('.*[?&]' + name + '=([^&#]*)'),
|
||||||
match = ''.concat(document.location.href, window.location.hash).match(re);
|
match = document.location.href.match(re);
|
||||||
|
|
||||||
if (match) {
|
if (match) {
|
||||||
// We have to decode the URL since want the cleartext value
|
// We have to decode the URL since want the cleartext value
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue