惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

N
News and Events Feed by Topic
GbyAI
GbyAI
博客园 - Franky
宝玉的分享
宝玉的分享
Blog — PlanetScale
Blog — PlanetScale
Google DeepMind News
Google DeepMind News
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
人人都是产品经理
人人都是产品经理
Microsoft Azure Blog
Microsoft Azure Blog
The Register - Security
The Register - Security
腾讯CDC
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
I
InfoQ
The Cloudflare Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Engineering at Meta
Engineering at Meta
MongoDB | Blog
MongoDB | Blog
有赞技术团队
有赞技术团队
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
F
Full Disclosure
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Vercel News
Vercel News
博客园 - 【当耐特】
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
S
Schneier on Security
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Project Zero
Project Zero
量子位
M
MIT News - Artificial intelligence
Stack Overflow Blog
Stack Overflow Blog
C
Cyber Attacks, Cyber Crime and Cyber Security
美团技术团队
Attack and Defense Labs
Attack and Defense Labs
C
Cybersecurity and Infrastructure Security Agency CISA
T
The Blog of Author Tim Ferriss
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
T
Troy Hunt's Blog
Google Online Security Blog
Google Online Security Blog
罗磊的独立博客
P
Proofpoint News Feed
Schneier on Security
Schneier on Security
Spread Privacy
Spread Privacy
S
SegmentFault 最新的问题
L
LINUX DO - 最新话题
Simon Willison's Weblog
Simon Willison's Weblog
爱范儿
爱范儿
博客园 - 聂微东
A
About on SuperTechFans
PCI Perspectives
PCI Perspectives
D
Docker

Minko Gechev's blog

skillgrade Unit Tests for AI Agent Skills You Should Care About AI Generative Development LLM-first Web Framework Reactive framework in ~200 lines of JavaScript Managing Angular Are LLMs going to replace us? Prefetching Heuristics Design Patterns in Open Source Projects - Part II Design Patterns in Open Source Projects - Part I What I learned doing 125 public talks - Part I Dynamic imports solve all the problems, right? 5 Angular CLI Features You Didn't Know About Angular quicklink Preloading Strategy Introducing Bazel Schematics for Angular CLI Building TypeScript Projects with Bazel Joining Google Playing Mortal Kombat with TensorFlow.js. Transfer learning and data augmentation Fast, extensible, configurable, and beautiful linter for Go Introducing Guess.js - a toolkit for enabling data-driven user-experiences on the Web Machine Learning-Driven Bundling. The Future of JavaScript Tooling. JavaScript Decorators for Declarative and Readable Code 3 Tricks For Using Redux and Immutable.js with TypeScript Follow Your Dream Career with Open Source. Personal Story. Redux Anti-Patterns - Part 1. State Management. Faster Angular Applications - Understanding Differs. Developing a Custom IterableDiffer Faster Angular Applications - Part 2. Pure Pipes, Pure Functions and Memoization Faster Angular Applications - Part 1. On Push Change Detection and Immutability Understanding Dynamic Scoping and TemplateRef Implementing a Simple Compiler on 25 Lines of JavaScript Developing Statically Typed Programming Language WebVR for a Gamified IDE 7 Angular Tools That You Should Consider Announcing ngrev - Reverse Engineering Tool for Angular Implementing Angular's Dependency Injection in React. Understanding Element Injectors. Distributing an Angular Library - The Brief Guide Angular in Production Ahead-of-Time Compilation in Angular 2.5X Smaller Angular 2 Applications with Google Closure Compiler Using Stripe with Angular (Deprecated) Building an Angular Application for Production Implementing the Missing "resolve" Feature of the Angular 2 Router Scalable Single-Page Application Architecture Managing ambient type definitions and dealing with the "Duplicate identifier" TypeScript error Static Code Analysis of Angular 2 and TypeScript Projects Enforcing Best Practices with Static Code Analysis of Angular 2 Projects ViewChildren and ContentChildren in Angular Dynamically Configuring the Angular's Router Angular 2 Hot Loader Lazy Loading of Route Components in Angular 2 Aspect-Oriented Programming in JavaScript Flux in Depth. Store and Network Communication. Using JSX with TypeScript Flux in Depth. Overview and Components. Even Faster AngularJS Data Structures Boost the Performance of an AngularJS Application Using Immutable Data - Part 2 Angular2 - First Impressions Build Your own Simplified AngularJS in 200 Lines of JavaScript Persistent State of ReactJS Component Boost the Performance of an AngularJS Application Using Immutable Data Processing Binary Protocols with Client-Side JavaScript Stream your Desktop to HTML5 Video Element Multi-User Video Conference with WebRTC Asynchronous calls with ES6 generators Binary Tree iterator with ES6 generators WebRTC chat with React.js AngularJS in Patterns (Part 3) AngularJS in Patterns (Part 2). Services. Using GitHub Pages with Jekyll! AngularJS in Patterns (Part 1). Overview of AngularJS Singleton in JavaScript Express over HTTPS What I get from the JavaScript MV* frameworks Remote Desktop Client with AngularJS and Yeoman The magic of $resource (or simply a client-side Active Record) AngularJS Inheritance Patterns AngularAOP v0.1.0 Advanced JavaScript at Sofia University AngularJS style guide Lazy prefetching of AngularJS partials Aspect-Oriented Programming with AngularJS CSS3 flipping effect Practical programming with JavaScript Why I should use publish/subscribe in JavaScript JavaScript, the weird parts Functional programming with JavaScript plainvm Looking for performance? Probably you should NOT use [].sort (V8) JavaScript image scaling ELang Caching CSS with localStorage Self-invoking functions in JavaScript (or Immediately Invoked Function Expressions) Asus N56VZ + Ubuntu 12.04 (en) Asus N56VZ + Ubuntu 12.04 Debian Squeeze + LXDE on Google Nexus S (or having some fun while suffering) HTML5 image editor Курсови проекти – ФМИ Carousel Gallery SofiaJS...
VNC client on 200 lines of JavaScript
2013-08-30 · via Minko Gechev's blog

Edit · Aug 30, 2013 · 8 minutes read · Follow @mgechev Computer science Development HTML5 HTTP JavaScript Node.js OpenSource vnc

In this quick blog post I’ll show you how to create a simple VNC client in about 200 lines of JavaScript. For our goal we’re going to use only HTML5 and JavaScript (client and server side). The end result will be something like this:

js-vnc

So, let’s begin!

Our application will have very simple architecture – a proxy server written in Node.js and a client in HTML5 and JavaScript. The Node.js server will stay between the browser and the VNC server. We need it because the client-side JavaScript does not supports TCP sockets so we can’t connect directly to the VNC server. The HTML5 client will have a canvas on which we will draw the frames we receive from the server. For VNC server you can use the free version of RealVNC.

First lets start with the server. Make sure you have node.js installed. We will use four node modules: rfb2, connect, socket.io and node-png.

Enter the following commands in your terminal:

cd ~
mkdir js-vnc
cd js-vnc
npm init

Now you should have folder called “js-vnc” with file named “package.json” in it. Use the following line of code to install almost all the dependencies:

npm install rfb2 connect socket.io --save

Unfortunately node-png is not in the npm registry and we have to build it from source. Use:

sudo npm install -g node-gyp
cd node_modules
git clone git@github.com:pkrumins/node-png.git
cd node-png
node-gyp configure build

and that was all…Now we can start creating our server. Let’s include all the required modules and initialize an array which will contains all the connected clients. Create a file called “server.js” in your project base directory and add the following content in it:

var rfb = require('rfb2'),
    socketio = require('socket.io').listen(8091, { log: false }),
    Png = require('./node_modules/node-png/build/Release/png').Png,
    connect = require('connect'),
    clients = [];

and now let’s create a static directory for our static resources:

now we can set the static directory of the connect server:

//creating new HTTP server. It will serve static files from the directory "./static" and will listen on 8090 port
connect.createServer(connect.static('./static')).listen(8090);

Now you can execute the following lines of code:

echo "Hello, world!" > ./static/index.html
node server.js

Now open: http://localhost:8091/index.html. If everything went well you should see the text: “Hello, world!”.

So far, so good! Now lets create a socket.io server. We want the client to connect to our proxy server and exchange data through socket.io with it. When the client is connected (i.e. the connection is established) it sends “init” message which contains the data required for the proxy server to connect to the VNC server – host, port and password. So let’s handle these events:

socketio.sockets.on('connection', function (socket) {
  socket.on('init', function (config) {
    var r = createRfbConnection(config, socket);
    socket.on('mouse', function (evnt) {
      r.pointerEvent(evnt.x, evnt.y, evnt.button);
    });
    socket.on('keyboard', function (evnt) {
      r.keyEvent(evnt.keyCode, evnt.isDown);
    });
    socket.on('disconnect', function () {
      disconnectClient(socket);
    });
  });
});

We already listen for incoming connections with socket.io on port 8091 so now we just subscribe to the “connection” event. When the client connects to our socket.io server we subscribe the server to the “init” event. It’s the message I told about earlier. Its parameters will contains data required for the proxy server to connect to the VNC server. In the callback associated with the “init” event we first create new RFB (remote framebuffer) connection. This method will return a RFB handler, we can use it to exchange data with the VNC server. After initialising the RFB connection we subscribe to three more events: “mouse”, “keyboard” and “disconnect”. The mouse event has parameter containing the position of the cursor and button state (1 for mouse down 0 for mouse up). For this demo we will support only the first mouse button. The keyboard event accepts as parameters the key code of the button which have triggered the event and flag which indicates whether the button is down or up. When the disconnect event happens we disconnect from the VNC server and release all the data for the current connection.

Here is posted the function createRfbConnection:

function createRfbConnection(config, socket) {
  var r = rfb.createConnection({
    host: config.host,
    port: config.port,
    password: config.password
  });
  addEventHandlers(r, socket);
  return r;
}

In the method “addEventHandlers” we add event handlers to the RFB handler we received from the “createConnection” method:

function addEventHandlers(r, socket) {
  r.on('connect', function () {
    socket.emit('init', {
      width: r.width,
      height: r.height
    });
    clients.push({
      socket: socket,
      rfb: r
    });
  });
  r.on('rect', function (rect) {
    handleFrame(socket, rect, r);
  });
}

When the RFB connection is established we send “init” event to the client (our browser) with the size of the screen. For simplicity we won’t scale the screen in the browser. After sending the “init” event to the browser we add the client to our connected clients and subscribe to the event “rect”. The “rect” event will trigger when there is a screen update. For extra simplicity we will use raw encoding.

The last method from our Node.js proxy we will look at is the “handleFrame” method.

function handleFrame(socket, rect, r) {
  var rgb = new Buffer(rect.width * rect.height * 3, 'binary'),
    offset = 0;

  for (var i = 0; i < rect.data.length; i += 4) {
    rgb[offset++] = rect.data[i + 2];
    rgb[offset++] = rect.data[i + 1];
    rgb[offset++] = rect.data[i];
  }

  var image = new Png(rgb, r.width, r.height, 'rgb');
  image = image.encodeSync();
  socket.emit('frame', {
    x: rect.x,
    y: rect.y,
    width: rect.width,
    height: rect.height,
    image: image.toString('base64')
  });
}

We create new binary buffer with size “rect.width * rect.height * 3” and process the received pixmap. After that we create new PNG image from it by specifying its width, height and format (rgb). We encode the image and emit the event “frame” with parameters the coordinates of the image, its size and the actual image in base64 format.

And we’re done with our server! There’s one detail which I missed with purpose. In this version we don’t use all the advantages of the RFB protocol. It has incremental frame transmitting which I’ve omitted. RFB implementation with excluded incremental transmission can be downloaded from here.

And now our simple client!

Edit the content of the file ./static/index.html with the following content:

<!DOCTYPE html>
<html>
    <head>
        <link rel="stylesheet" href="./css/bootstrap.min.css" />
        <link rel="stylesheet" href="./css/styles.css" />
    </head>
    <body>
        <div class="form-wrapper" id="form-wrapper">
            <form class="form-horizontal" onsubmit="return false">
              <div class="control-group">
                <label class="control-label" for="host">Host:</label>
                <div class="controls">
                  <input type="text" id="host" name="host" value="192.168.100.6" /><br />
                </div>
              </div>
              <div class="control-group">
                <label class="control-label" for="port">Port:</label>
                <div class="controls">
                  <input type="text" id="port" name="port" value="5900" /><br />
                </div>
              </div>
              <div class="control-group">
                <label class="control-label" for="port">Port:</label>
                <div class="controls">
                  <input type="password" id="password" name="password" value="paralaks" /><br />
                </div>
              </div>
              <div class="control-group">
                <div class="controls">
                  <button class="btn" id="loginBtn">Log in</button>
                </div>
              </div>
            </form>
        </div>
        <canvas id="screen">
        </canvas>
        <script src="http://localhost:8091/socket.io/socket.io.js"></script>
        <script src="./js/Client.js"></script>
    </body>
</html>

We’ve added simple bootstrap form and a canvas. Now create sub-directory of our project base directory: “./static/js” and create the file “Client.js” in it.

We will wrap the whole code in an IIFE to omit any globals. Let’s create a simple object which will hold our settings:

(function () {
  var Config = {
    URL: 'http://localhost:8091'
  };
  //..more code here
}());

Now lets add some code for initialisation after the “Config” declaration:

document.getElementById('loginBtn').addEventListener('click', function () {
    var screen = new Screen(document.getElementById('screen')),
      client = new Client(screen);

    client.connect({
      host: document.getElementById('host').value,
      port: parseInt(document.getElementById('port').value, 10),
      password: document.getElementById('password').value,
      callback: function () {
        document.getElementById('form-wrapper').classList.add('form-wrapper-hidden');
      }
    });
  }, false);

We simply add event handler on click of the button with id “loginBtn”. In the callback we initialise new “Screen” object with our canvas as argument of the constructor function and create new “Client” with the Screen object as parameter. We call the connect method of the client with the specified parameters - host, port, password and a callback which will be invoked when the client is being connected.

This is the definition of the connect method:

Client.prototype.connect = function (config) {
    var self = this;
    this._socket = io.connect(Config.URL);
    this._socket.emit('init', {
      host: config.host,
      port: config.port,
      password: config.password
    });
    this._addHandlers(config.callback);
    this._initEventListeners();
  };

It creates new socket.io connection with the server’s socket.io server host name and port and emits the “init” event we already talked about (contains data required for connection to the VNC server). After that we add handlers which will listen for events coming from the server - the “init” and “rect” event. Since the “rect” event is a bit more interesting we will look in its handling:

this._socket.on('frame', function (frame) {
    that._screen.drawRect(frame);
  });

As you see we simply delegate the drawing to the screen and pass it the frame as argument. And here is the actual drawing on the canvas:

Screen.prototype.drawRect = function (rect) {
    var img = new Image(),
      that = this;
    img.width = rect.width;
    img.height = rect.height;
    img.src = 'data:image/png;base64,' + rect.image;
    img.onload = function () {
      that._context.drawImage(this, rect.x, rect.y, rect.width, rect.height);
    };
  };

We create new image with the frame’s width and height as dimensions and we set image’s src to be equal to the frame’s base64 image. After that, when the image is loaded we draw it onto the canvas.

The last interesting moment is the handling of the mouse and keyboard events. Of course we delegate it to the screen object:

Client.prototype._initEventListeners = function () {
    var self = this;
    this._screen.addMouseHandler(function (x, y, button) {
      self._socket.emit('mouse', {
        x: x,
        y: y,
        button: button
      });
    });
    this._screen.addKeyboardHandlers(function (code, isDown) {
      self._socket.emit('keyboard', {
        keyCode: code,
        isDown: isDown
      });
    });
  };

As you see we add the mouse and keyboard event handlers and in the event callbacks we simply emit new socket.io events, respectively: “mouse” and “keyboard”, with their arguments. For simplicity our canvas is absolutely positioned and has coordinates (0, 0).

And…well…thats all! We have ready to go VNC client in just few lines of JavaScript!

The source code from this article can be found in my GitHub account.

Quick video demo