Conversation
@ibeckermayer could you use some feature branch for this POC? (we will review it when it's ready for production) |
btw, check out built-in browser APIs for |
Sure
That's a much better solution... you would think this would pop up on the front page of a google search for "javascript encode string to utf8" |
also, check out our testing (unfinished) guide :) https://gravitational.slab.com/posts/testing-guideline-proposal-wip-yuc9uvwa |
…e jsdom hasn't caught up to yet
dc5280e
to
d13b31d
Compare
Unfortunately jsdom hasn't implemented TextEncoder either (jsdom/jsdom#2524), so I'm skipping those username/pwd tests. I think there is a way that I can effectively mock it and replace it with the In the meantime, I double checked that all works as expected by converting those skipped tests into something that can be run by copy-pasting into the browser console. Any thoughts or previous experience regarding handling jsdom deficiencies? var MessageType = {
CLIENT_SCREEN_SPEC: 1,
PNG_FRAME: 2,
MOUSE_MOVE: 3,
MOUSE_BUTTON: 4,
KEYBOARD_INPUT: 5,
CLIPBOARD_DATA: 6,
USERNAME_PASSWORD_REQUIRED: 7,
USERNAME_PASSWORD_RESPONSE: 8,
};
var Codec = /** @class */ (function () {
function Codec() {}
// encUsernamePassword encodes the username and password response
Codec.prototype.encUsernamePassword = function (username, password) {
// Encode username/pass to utf8
var encoder = new TextEncoder();
var usernameUtf8array = encoder.encode(username);
var passwordUtf8array = encoder.encode(password);
// initialize buffer and corresponding view
// numbers correspond to message spec
// | message type (8) | user_length uint32 | username []byte | pass_length uint32 | password []byte
var bufLen =
1 + 4 + usernameUtf8array.length + 4 + passwordUtf8array.length;
var buffer = new ArrayBuffer(bufLen);
var view = new DataView(buffer);
var offset = 0;
// set data
view.setUint8(offset++, MessageType.USERNAME_PASSWORD_RESPONSE);
view.setUint32(offset, usernameUtf8array.length);
offset += 4; // 4 bytes to offset 32-bit uint
usernameUtf8array.forEach(function (byte) {
view.setUint8(offset++, byte);
});
view.setUint32(offset, passwordUtf8array.length);
offset += 4;
passwordUtf8array.forEach(function (byte) {
view.setUint8(offset++, byte);
});
return buffer;
};
return Codec;
})();
var codec = new Codec();
// Create test vals + known UTF8 encodings
const username = "Hello";
const usernameUTF8 = [0x0048, 0x0065, 0x006c, 0x006c, 0x006f];
const password = "world!*@123";
const passwordUTF8 = [
0x0077, 0x006f, 0x0072, 0x006c, 0x0064, 0x0021, 0x002a, 0x0040, 0x0031,
0x0032, 0x0033,
];
// Encode test vals
const message1 = codec.encUsernamePassword(username, password);
const view1 = new DataView(message1);
// Walk through output
let offset1 = 0;
if (view1.getUint8(offset1++) != MessageType.USERNAME_PASSWORD_RESPONSE) {
throw new Error("Test Failed!");
}
if (view1.getUint32(offset1) != usernameUTF8.length) {
throw new Error("Test Failed!");
}
offset1 += 4;
usernameUTF8.forEach((byte) => {
if (view1.getUint8(offset1++) != byte) {
throw new Error("Test Failed!");
}
});
if (view1.getUint32(offset1) != passwordUTF8.length) {
throw new Error("Test Failed!");
}
offset1 += 4;
passwordUTF8.forEach((byte) => {
if (view1.getUint8(offset1++) != byte) {
throw new Error("Test Failed!");
}
});
const first3RangesString = "\u0000\u007F\u0080\u07FF\u0800\uFFFF";
const first3RangesUTF8 = [
0x00, 0x7f, 0xc2, 0x80, 0xdf, 0xbf, 0xe0, 0xa0, 0x80, 0xef, 0xbf, 0xbf,
];
const message2 = codec.encUsernamePassword(
first3RangesString,
first3RangesString
);
const view2 = new DataView(message2);
let offset2 = 0;
if (view2.getUint8(offset2++) != MessageType.USERNAME_PASSWORD_RESPONSE) {
throw new Error("Test Failed!");
}
if (view2.getUint32(offset2) != first3RangesUTF8.length) {
throw new Error("Test Failed!");
}
offset2 += 4;
first3RangesUTF8.forEach((byte) => {
if (view2.getUint8(offset2++) != byte) {
throw new Error("Test Failed!");
}
});
if (view2.getUint32(offset2) != first3RangesUTF8.length) {
throw new Error("Test Failed!");
}
offset2 += 4;
first3RangesUTF8.forEach((byte) => {
if (view2.getUint8(offset2++) != byte) {
throw new Error("Test Failed!");
}
}); |
As an aside, do you understand why the JS ecosystem needs a project like JSDOM at all? Naively -- v8 is open source, why can't Jest just use it to emulate the chrome browser precisely? |
@ibeckermayer you can mock these APIs in your tests with something like this https://github.com/gravitational/webapps/blob/master/packages/teleport/src/lib/term/protobuf.js#L178 Or you can mock it with nodejs one const { TextEncoder } = require('util');
window.TextEncoder = TextEncoder; As for JSDOM, nodejs does not have any renderer (html) and it's pure V8 engine. So to test UI layer you need something like JSDOM to mock DOM APIs. |
Oh I see, I was confused about the different parts involved. Still, why don't some wizards pull out the part of Chrome that implements the DOM API, rather than reimplement everything with JSDOM? Or why is that the wrong question? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we change the directory name from tdp
to rdp
? :-)
I saw your reply about |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM after comments are addressed
Implements client side codec for the custom desktop access protocol (RFD 37). Clipboard was ignored for now as that part of the protocol needs revision.
UTF-8 encoder (
stringToUtf8ByteArray
) was ripped from the google closure-library