Using a remote PC's Windows OS by Node.js

This WebSocket server for Version 3.0 of Smart Mobile Studio is based on the server on the preceding page. It is conveniently set up for use on the local machine with the same client as detailed on the preceding page. Commands are executed on the server and the result is both shown in the server console and returned to the client. The javascript code in the asm block makes use of the fact that an object parameter (objResult) behaves as a var parameter in Pascal; a change made to it within the function is reflected in the original object passed to the function. Commands used in our testing include dir, echo Hello!, ipconfig, node -help, node -v, path and whoami.

Our original page contents from 2015 follows the Smart Pascal code of the server.

Code of Unit 1 of Server

unit Unit1;

// Note: Install the "ws" nodejs module in your output folder before running this demo. 
// 1. Change directory to the output folder (compile to create it first)
// 2. Execute the following via shell:  npm install ws

interface

uses
  System.Types, System.Types.Convert, System.Objects, Nodejs.websocket,
  SmartNJ.System, SmartNJ.Network, SmartNJ.Server.Http, SmartNJ.Server.WebSocket,
  NodeJS.child_process, System.Time;

type
  TMessageServer = class(TObject)
  private
    FServer: TNJWebSocketServer;
    procedure HandleTextMessage(Sender: TObject; Socket: TNJWebSocketSocket; Info: TNJWebsocketMessage);
  public
    procedure Run;
    constructor Create; virtual;
    destructor Destroy; override;
  end;

implementation

constructor TMessageServer.Create;
begin
  inherited Create;
  FServer := TNJWebSocketServer.Create;
end;

destructor TMessageServer.Destroy;
begin
  if FServer.Active then
    FServer.Active := false;
  FServer.free;
  inherited;
end;

procedure TMessageServer.Run;
begin
  FServer.Port := 1881;
  // Setup an event handler for text messages.
  FServer.OnTextMessage := @HandleTextMessage;  
  FServer.Active := true;
  WriteLn('Server started, listening to port 1881');    
end;   

procedure TMessageServer.HandleTextMessage(Sender: TObject; Socket: TNJWebSocketSocket; Info: TNJWebsocketMessage);
var
  output, command: String;
begin
  command := Info.wiText; 
  WriteLn('Received command: ' + command);
  asm
    var objResult = {op: "No result"};    
    const exec = require('child_process').exec;
    exec(@command, objResult, function(err, stdout, stderr) {
      if (err) {
        console.log('error code: ' + err.code);
        return;
      }
      console.log(stdout);
      objResult.op = stdout;
    });
    setTimeout(function(){@output = objResult.op;}, 400);            
  end;
  var Timer := TW3Timer.Create(Self);
  Timer.Delay := 500; 
  Timer.OnTime := procedure(Sender: TObject) begin Socket.Send(output); Timer.Enabled := False; end;
  Timer.Enabled := True;  
end;

end.
    

Using a remote PC's Windows OS by Node.js using early versions of Smart Mobile Studio

This Smart Pascal program, inspired by James's remote control of the Raspberry Pi, uses Node.js to execute commands on a remote Windows PC. It has a similar set-up to that described in detail above. As with the Pi version, the user has the choice of selecting one of the tested commands in the combo box or typing a command into the edit box. The commands received from all clients are collected in a single file on the server.

The Smart Pascal code of the server and client follow the screenshot of the program in action. See the Pi version for the XML code of the form of the client.

Application in Action

Application in Action

Smart Pascal Code of Server

You can compare this code with a JavaScript version.

unit Unit1;

interface

type
  TServer = class
  public
    procedure Run;
  end;

implementation

uses
  NodeJS.Core, NodeJS.http, Node_Static, socket.io, NodeJS.fs;

procedure TServer.Run;
begin
  var ws := fs.createWriteStream('requests.txt', nil);

  var fileserver := TNodeStaticServer.Create('./public');
  //start http server
  var server: JServer := http.createServer(
    procedure(request: JServerRequest; response: JServerResponse)
    begin
      Console.log('http request: ' + request.url);
      if request.url = '/' then
        request.url := '/WinClientExec.html';
      fileserver.serve(request, response);
    end);
  server.listen(8079, '');
  Console.log('Server running at http://192.168.0.2:8079');

  var value := 0;
  var io := socketio().listen(server);
  io.sockets.on('connection',    // waiting for connections
    procedure(socket: JSocketIO)
    begin
      socket.on('requestFromClient',  // waiting for a special request from the client
        procedure(data: Variant; callback: JSocketIODataFunction)
        begin
          Console.log('Received from client: ' + data);
          ws.write(data + #13#10, 'utf8');
          asm
            var exec = require('child_process').exec;
            exec(data, function(err, stdout, stderr) {
              if (err) {
                @callback('error code ' + err.code);
                return;
              }
              @callback(stdout);
            });
          end;
      end);
    end);
end;

end.    

Smart Pascal Code of Form of Client

unit Form1;

interface

uses 
  SmartCL.System, SmartCL.Graphics, SmartCL.Components, SmartCL.Forms, 
  SmartCL.Fonts, SmartCL.Borders, SmartCL.Application, SmartCL.Controls.Button,
  SmartCL.Controls.Panel, SmartCL.Controls.EditBox, SmartCL.Controls.Memo,
  socketioclient, SmartCL.Controls.ComboBox;

type
  TForm1 = class(TW3Form)
    procedure W3Button1Click(Sender: TObject);
  private
    FSocket: JSocketIO;
    {$I 'Form1:intf'}
  protected
    procedure InitializeObject; override;
  end;

implementation

procedure TForm1.W3Button1Click(Sender: TObject);
begin
  FSocket.emit('requestFromClient', [W3EditBox1.Text],
    procedure(aData: variant)
    begin
      W3Memo1.Text := aData;
    end);
end;

procedure TForm1.InitializeObject;
begin
  inherited;
  {$I 'Form1:impl'}
  FSocket := socketio.connect('http://192.168.0.2:8079');
  W3Combobox1.Add('echo This is fun!');
  W3Combobox1.Add('dir');
  W3Combobox1.Add('type requests.txt');
  W3Combobox1.Add('ipconfig');
  W3Combobox1.Add('path');
  W3Combobox1.Add('ver');
  W3Combobox1.Add('node -v');

  W3Combobox1.OnClick := procedure(Sender: TObject)
    begin
      FSocket.emit('requestFromClient', [W3Combobox1.Items[W3Combobox1.SelectedIndex]],
        procedure(aData: variant)
        begin
          W3Memo1.Text := aData;
        end);
    end;
  W3Editbox1.Text := 'dir';
end;

initialization
  Forms.RegisterForm({$I %FILE%}, TForm1);
end.    

Programming - a skill for life!

Using Node.js for a server that updates connected clients in real time and remote use of a Raspberry Pi's OS