Node.js



Getting started

Starting from 20.5.0 version BAS introduces possibility to run arbitrary javascript code on node.js platform and install npm modules. BAS had javascript support before this update, but language version that BAS uses is a bit outdated, also embedding node.js gives possibility to install and use enormous amount of modules. So it is big update which opens great opportunities.

You need to use “Embedded languages” module to start with node.js:



Using of node.js is optional. Some scripts may use embedded language, some may use only old js implementation. To include node.js inside the script you are working at, need to enable it in settings panel.



After running script for the first time with node.js enabled, BAS will install language distributive and displays following window while doing that:



Installing language needed only on first run or when module list is changed, so when you restart BAS, it will start instantly second time.

Embedded languages may be used inside compiled script as well, and installing language distro is also performed after running compiled script for the first time.

When launching script with js enabled, you can finally add code through “Node.js” action, here is how code editor look like:



Start with simple example and put inside code editor.

console.log("Hello")

Function log from console class is integrated into BAS environment, and running it will display message inside “Log” panel.



Of course, code can be more complex, here is example, which calculates sha256 checksum:

const crypto = require('crypto');

const secret = 'abcdefg';
const hash = crypto.createHmac('sha256', secret)
                   .update('I love cupcakes')
                   .digest('hex');
console.log(hash);



Data exchange

BAS variables can be used to send data inside embedded code.

If you want to make hash from values, which user inputs or which is obtained in other way, you need to set variable and use it like this:

const crypto = require('crypto');

const secret = 'abcdefg';
const hash = crypto.createHmac('sha256', secret)
                   .update([[DATA_TO_HASH]])
                   .digest('hex');
console.log(hash);



Variables also may transferred data to BAS script, so you can save hash result into new variable and use it inside BAS script when hash will be computed.

const crypto = require('crypto');

const secret = 'abcdefg';
[[RESULT]] = crypto.createHmac('sha256', secret)
                   .update([[DATA_TO_HASH]])
                   .digest('hex');
console.log(hash);

Adding external libraries

Npm is a package manager for the javascript and you can use any package inside BAS.

Open settings dialog again and click on “+ Add npm module” button.



This will bring module find dialog.


After changing module list, BAS will run install procedure again, later you can use your newly installed module inside any action.

Synchronizing

Most of javascript functions are non blocking. Consider following example:

fs.readFile('file.txt', 'utf8', function(err, contents) {
    //Executed second
    console.log("Done");
});
//Executed first
console.log("Next")

This example reads file and has one peculiarity - the order in which code will be executed. Despite the order in which functions are written(first “Done” and than “Next”), this code will be executed in reverse order(first “Next” and second “Done”). This happens because of readFile function, it starts operation and returns immediately, and only when operation of reading file is complete, callback function will be called. Most of node.js functions which may take some time uses that approach.

This reverse order has its own advantages, but may lead to quite ugly code, especially if you need to complete sequence of asynchronous operations:

fs.readFile('file1.txt', 'utf8', function(err, contents) {
    fs.readFile('file2.txt', 'utf8', function(err, contents) {
        fs.readFile('file3.txt', 'utf8', function(err, contents) {
            //And so on...
        });
    });
});

This may be solved with async and await keywords. With help of this keywords you may “synchronize” any function, like this:

await (new Promise((resolve, reject) => {
    fs.readFile('file3.txt', 'utf8', function(err, contents) {
        resolve()
    });
}));

The idea is to add lines, which are marked red and place resolve() call in the place, where block execution will end. After synchronization, you can be sure, that next line will wait until previous block will finish.

await (new Promise((resolve, reject) => {
    fs.readFile('file3.txt', 'utf8', function(err, contents) {
        resolve()
    });
}));
console.log("File Reading done")

BAS has “Synchronize” shortcut, so you don't have to copy/paste this code:

“reject” function may be used with “resolve” to throw exception from synchronized function.

await (new Promise((resolve, reject) => {
    fs.readFile('file3.txt', 'utf8', function(err, contents) {
        if(err)
        {
            reject(err)
            return;
        }
        resolve()
    });
}));

You may read this article to know more about async/await paradigm https://hackernoon.com/6-reasons-why-javascripts-async-await-blows-promises-away-tutorial-c7ec10518dd9

BAS marks every internal functions as async and it is strongly advised to use same approach during adding custom code



Calling api

You can call any BAS action from node.js code, including working with browser.

Here is example which loads google.com

await BAS_API("load('google.com')!");

To call any BAS api method, you need to use BAS_API function and pass string with api string.

Fortunately, you can just copy and paste any action right into code editor.

In that way you can write script entirely on node.js.

Calling BAS functions

You can also call BAS functions and obtain result.

var Result = await BAS_FUNCTION(
	"FunctionName", 
	{ParamName1: "ParamValue1", ParamName2: "ParamValue2"}
)

Suppose you have a simple BAS function “LoadUrl”, it takes one argument, which is called “Url”, and returns page content, which specified url points to. Internally it opens that page in a browser and uses “Page Html” action to obtain result:

We will call function “LoadUrl” with parameter “ip.bablosoft.com”, like this:

var PageContent = await BAS_FUNCTION(
	"LoadUrl", 
	{Url: "ip.bablosoft.com"}
)

In case if specified url doesn't exist, BAS function will fail, and you can process that error with a help of try/catch keywords:

try
{
    var PageContent = await BAS_FUNCTION("LoadUrl", {Url: "ip.bablosoft.com"})
    console.log("Page content is " + PageContent)
}catch(e)
{
    console.log("Function finished with error " + e)
}

Files

Functions, which are defined inside one action can't be used inside other action, but you can move frequently used code inside file and call it later. This is analog of function, just for whole script:

Examples

Reading xlsx files with xlsx module

var XLSX = require('xlsx');
var workbook = XLSX.readFile([[FILE_LOCATION]]);

var first_sheet_name = workbook.SheetNames[0];
 
var worksheet = workbook.Sheets[first_sheet_name];

[[RESULT_CSV]] = XLSX.utils.sheet_to_csv(worksheet).split("\n")



Executing mysql query with mysql2 module

// get the client
const mysql = require('mysql2');

// create the connection to database
const connection = mysql.createConnection({
    host: 'localhost',
    user: 'root',
    database: 'server',
    password: 'test'
});

console.log("making request")

await(new Promise((resolve, reject) => {
    connection.query(
        'SELECT * FROM `users`',
        function (err, results, fields) {
            if(err)
            {
                reject(err)
                return
            }
            
            [[RESULTS]] = results
            resolve()
        }
    ); 
}));



Making post to facebook through fb module

var FB = require('fb');

var token = "YOUR TOKEN"
FB.setAccessToken(token);



await(new Promise((resolve, reject) => {
    var body = 'POST BODY';
    FB.api('me/feed', 'post', { message: body }, function (res) {
        if (!res || res.error) {
            reject(!res ? 'error occurred' : res.error);
            return
        }
        console.log('Post Id: ' + res.id);
        resolve()
    });

}));



Get ftp directory listing ftp module

var Client = require('ftp');

var c = new Client();
c.connect();

await(new Promise((resolve, reject) => {
    c.on('ready', function () {
        c.list(function (err, list) {
            if (err)
            {
                reject(err)
                return
            }
            [[DIRECTORY_LIST]] = list
            console.log([[DIRECTORY_LIST]])
            c.end();
            resolve()

        });
    });
}));