Do I need to do anything specific with Node's HTTPS to get a response?


#1

Hello,

I am new to Electron and I’m trying to get some Node code that works when the script handling a HTTPS POST connection is called from a Unix command line but the same code fails to enter a particular execution branch when used in a Renderer window.

I’ve created a simple App, with a basic html form in the Renderer window to get a username and password. This data is sent to my custom postRequest function which builds up a HTTPS object which is then sent to a RestFul api server. where I should get a response.

What happens in the Renderer window, is that the Console.log(“test 1”) and Console.log(“test 5”) are printed out. The HTTPS object is instantiated and so is the self.req object, BUT this block of code does not appear to be executed.

self.req = https.request(self.options, function(res) { 
....
}

I only say this because no console.logs that are in this block of code are outputted and I can’t see any messages being sent from the Electron app.

Do I have to do some special with HTTPS calls within Electron ?
a) Such as Intercept them as standard, if so how do I do this ?
b) am I missing some configuration data ?
c) Do HTTPS calls within the main process rather than a Renderer window, ?
d) Use remote calls within the Renderer window to get HTTPS connections working ?

Here’s the code excerpt.

Connection.prototype.postRequest = function (uri, formData, opts, cb) {
    var self = this;
    console.log("connection::postRequest -start");
    console.log("uri: " + uri);
    console.log(formData);
    console.log(opts);

    self.data = querystring.stringify(formData);
    self.setAppKey();
    self.options = {
        hostname: urls.com(), //urls.com() returns a string 'identitysso.betfair.com'
        port: 443,
        path: uri,
        method: 'POST',
        agent: ssl.getHttpsAgent(), //using Agent, connection defaults to keep-alive
        headers: {
            'X-Application' : self.appkey,
            'Accept': 'application/json',
            'Content-Length': self.data.length
        }
    }

    if (opts.ssid) {
        self.options.headers['X-Authentication'] = opts.ssid;
    }

    if (opts.contentType) {
        self.options.headers['Content-Type'] = opts.contentType;
    }
    else {
        self.options.headers['Content-Type'] = 'application/json';
        self.options.headers['Accept'] = 'application/json';
    }

    console.log(self.options);
    console.log("test 1");
    self.dataStr = "";

    if (https) {
        console.log("https obj is present");
    } else {
        console.log("https obj not present");
    }

    self.req = https.request(self.options, function(res) {
        console.log("test 2");
        res.on('data', function(data) {
            console.log("test 3");
            self.dataStr += data;
            console.log("test 4");
        });
        res.on('end', function() {
            console.log("got to the end of the dataStr");
            self.dataStr = JSON.parse(self.dataStr);
            console.log("dataStr start");
            console.log(self.dataStr);
            console.log("dataStr end");
            var result = {
                body: self.dataStr
            }
            console.log("result - start");
            console.log(result);
            console.log("result - end");
            cb(null, result);
        });
    });


    if (self.req) {
        console.log("self.req is present");
    }
    else {
        console.log("self.req is not present");
    }

    self.req.on('error', function(err){
        console.log("req err: " + err);
    });
    console.log("test 5");
    self.req.end(self.data);
    console.log("connection::postRequest - end");
};

Can someone see what I’m doing wrong or put a simple working version of HTTPS post call being made within Electron ?


#2

I’ve finally got this little b*gger working.

To help those who have asked questions before about Node’s HTTPS usage in Electron and had to find alternative modules or use JQuery.

The basic answer that I’ve found is that you shouldn’t use Node’s HTTPS module functionality within the Renderer process, but the actual call/processing should be handled within the Main process.

The project I’m dealing with is making HTTPS calls to a RESTful API service at Betfair.com (an online sports exchange website). The HTTPS call that I make have to send a self signed cert and key to the service.

The code that I’ve put up here, is very basic to just make a login request and to get a response back. The code only does basic error checking, as is provided as is.

I handled this by sending a IPC message from the Renderer window along with the username, password values entered in a HTML page, to the main process. The IPC call is sent from login-screen.js when a button is clicked and is received in main.js. All the other files is what I used in my little test app. Hopefully, someone else finds this useful.

Sorry if some code is bunched together, the formatting on this page, went wrong and I had to edit this post to make it look ok.

My main.js code is this.

var app = require("app");
var browserWindow = require("browser-window");
const ipcMain = require("electron").ipcMain;
var Login = require("./login");

var mainWindow = null;

app.on('window-all-closed', function() {
    if (process.platform != 'darwin') {
        app.quit();
    }
});

app.on('ready', function() {
    mainWindow = new browserWindow({width: 800, height: 600, 'dark-theme': true});
    mainWindow.loadURL('file://' + __dirname + '/index.html');

    var htmlContents = mainWindow.webContents;

    // console.log(htmlContents);

    ipcMain.on("login-attempt", function(event, username, password) {

        function callback(err, result) {
            if (err) {
                console.error("An error occured");
                event.sender.send("login-attempt-fatal-error");
            }

            console.log("ipc received: user: " + username + " password: " + password);

            if (result.Upper(result.result) === "SUCCESS") {
                console.log("Login Successful");
                event.sender.send("login-attempt-success");
            }
            else {
                console.log("Login Error");
                event.sender.send("login-attempt-error");
            }
        }

        Login.login(username, password, callback);
    });

    mainWindow.openDevTools();
    mainWindow.on('close', function() {
        mainWindow = null;
    });
});

login-screen.js

    var ipcRenderer = require("electron").ipcRenderer;
    window.onload = function() {
        var main = document.getElementById("main");
        main.innerHTML = "";
        var loginDiv = document.createElement("div");
        loginDiv.setAttribute("id", "loginDiv");
        var form = document.createElement("form");
        var loginLabel = document.createElement("label");
            loginLabel.setAttribute("for", "login");
            loginLabel.innerHTML = "Login: ";
        var loginInput = document.createElement("input");
            loginInput.setAttribute("type", "text");
            loginInput.setAttribute("name", "login");
            loginInput.setAttribute("id", "login");
        var passwordLabel = document.createElement("label");
            passwordLabel.setAttribute("for", "password");
            passwordLabel.innerHTML = "Password: ";
        var passwordInput = document.createElement("input");
            passwordInput.setAttribute("type", "text");
            passwordInput.setAttribute("name", "password");
            passwordInput.setAttribute("id", "password");
        var button = document.createElement("button");
            button.setAttribute("name", "loginButton");
            button.innerHTML = "Log In";
            button.onclick = function() {
                var username = "";
                var password = "";
                if (document.getElementById("login") && (document.getElementById("password"))) {
                    username = document.getElementById("login").value;
                    password = document.getElementById("password").value;
                }
                if ((username) && (password)) {
                    console.log("sending login-attempt to main via ipc");
                    ipcRenderer.send("login-attempt", username, password);
                }
          };
        main.appendChild(loginDiv);
        loginDiv.appendChild(form);
        form.appendChild(document.createElement("br"));
        form.appendChild(loginLabel);
        form.appendChild(loginInput);
        form.appendChild(document.createElement("br"));
        form.appendChild(document.createElement("br"));
        form.appendChild(passwordLabel);
        form.appendChild(passwordInput);
        form.appendChild(document.createElement("br"));
        form.appendChild(document.createElement("br"));
        form.appendChild(button);
    }

login.js:

var urls = require('./urls.js');
var routes = require('./routes.js');
var connection = require("./test-connection.js");

var Login = function () {};

Login.prototype.login = function (username, password, cb) {
    console.log("Login::botLogin - start");
    var self = this;
    self.opts = {
        'contentType': 'application/x-www-form-urlencoded',
        ssid: null
    }

    self.formData = {
        username: username,
        password: password
    }

    self.uri = routes.botlogin();

    function callback(err, result) {
        console.log("Login::callback - start");
        if (err) {
            console.log(err);
            cb(err);
            return;
        }
        console.log(result);
        cb(null, result);
        console.log("Login::callback - end");
    }

    connection.postRequest(self.uri, self.formData, self.opts, callback);
    console.log("Login::botLogin -end");
};

module.exports = new Login();

urls.js

var BetfairUrls = function() {};

BetfairUrls.prototype.com = function() {
    return 'identitysso.betfair.com';
};

module.exports = new BetfairUrls();

routes.js

var Routes = function() {};

Routes.prototype.botlogin = function() {
    return '/api/certlogin';
};

Routes.prototype.keepalive = function() {
    return '/api/keepAlive';
};

Routes.prototype.logout = function() {
    return '/api/logout';
};

module.exports = new Routes();

test-connection.js

var ssl = require('./ssl-options.js');
var urls = require('./urls.js');
var https = require('https');
var querystring = require('querystring');
var Connection = function () {};
Connection.prototype.setSSID = function (ssid) {
    var self = this;
    console.log("connection::setSSID -start");
    self.ssid = ssid;
    console.log("connection::setSSID -end");
};
Connection.prototype.setAppKey = function() {
    var self = this;
    console.log("connection::setAppKey - start");
    self.appkey = "electron-test-node-js";
    console.log("connection::setAppKey - end");
};
Connection.prototype.postRequest = function (uri, formData, opts, cb) {
    var self = this;
    console.log("connection::postRequest -start");
    self.data = querystring.stringify(formData);
    self.setAppKey();
    self.options = {
        hostname: urls.com(),
        port: 443,
        path: uri,
        method: 'POST',
        agent: ssl.getHttpsAgent(), //using Agent, connection defaults to keep-alive
        headers: {
            'X-Application' : self.appkey,
            'Accept': 'application/json',
            'Content-Length': self.data.length
        }
    }
    if (opts.ssid) {
        self.options.headers['X-Authentication'] = opts.ssid;
    }
    if (opts.contentType) {
        self.options.headers['Content-Type'] = opts.contentType;
    }
    else {
        self.options.headers['Content-Type'] = 'application/json';
        self.options.headers['Accept'] = 'application/json';
    }
    self.dataStr = "";
    self.req = https.request(self.options, function(res) {
        res.on('data', function(data) {
            self.dataStr += data;
        });
        res.on('end', function() {
            self.dataStr = JSON.parse(self.dataStr);
            var result = {
                body: self.dataStr
            }
            console.log(result);
            cb(null, result);
        });
    });
    self.req.on('error', function(err){
        console.log("req err: " + err);
    });
    self.req.end(self.data);
    console.log("connection::postRequest - end");
};
module.exports = new Connection();

ssl-options.js

var https = require('https');
var fs = require('fs');
var SSLOptions = function() {};
SSLOptions.prototype.getHttpsAgent = function() {
    var self = this;
    self.certPath = "./bot/client-2048.crt";
    self.keyPath = "./bot/client-2048.key";
    self.key = fs.existsSync(self.keyPath) && fs.readFileSync(self.keyPath);
    self.cert = fs.existsSync(self.certPath) && fs.readFileSync(self.certPath);
    var agent;
    if (self.key && self.cert) {
        agent = new https.Agent({
            cert: self.cert,
            key: self.key
        });
    }
    return agent;
};
module.exports = new SSLOptions();

index.html

<!DOCTYPE html>
<html>
<head>
<title>Test Login</title>
</head>
<body>
    <script type="text/javascript" name="scripts">
        require("./login-screen.js");
    </script>
    <div id="topMenu"></div>
    <div id="leftMenu"></div>
    <div id="main"></div>

</body>
</html>