cancel
Showing results for 
Search instead for 
Did you mean: 
SOLVED

How to install NodeJS packages?

How to install NodeJS packages?

suitendaal
Established Member

Hi,

How do I install NodeJS packages in WebIQ to use in the Code Manager?
I currently want to use it to be able to use the NodeJS fs package to access the client's file system to read the content of files, which is not possible with only JavaScript.
Thanks!

17 REPLIES 17

Sgilk
Contributor

Hi @suitendaal ,

This is not possible as NodeJS is a server side tool and is actually a Javascript runtime in itself. The WebIQ runtime does not permit installation of NodeJS packages.

Generally, web clients prohibit access to the file system for security reasons. Imagine if you accessed a web page that started to read files off of your local PC.

If you could share a little more of what you are trying to accomplish, maybe I could help provide a solution.

suitendaal
Established Member

Hi @Sgilk ,

Thank you for your answer. I already suspected this and understand the security issues.

We are using WebIQ as an HMI for a robot-integrated system. The operator can create recipes/jobs for the robot via a third party tool (Wepall), hosted in a web page. We can show this website within our WebIQ HMI and the operator can download the generated file as a .zip file to the client pc. We now want the operator to be able to upload the downloaded and unzipped files to the robot, using the WebIQ HMI. Uploading can be done using the robot's FTP server, but we need to find a way to select and unzip the downloaded files and place them in the FTP folder.
The robot, CtrlX PLC and client PC all are on the same local network. If it is not possible to access the filesystem on the client PC, perhaps it is possible to upload the .zip file to the PLC in the WebIQ HMI and run a script to unzip the files and put them in the PLC's FTP folder which then synchronizes with the robot? For example, can I make use of the

<input type="file">

 HTML tag?

bostroemc
Occasional Contributor

I had a similar use case where I needed to transfer Python scripts from the local PC to the CORE itself.  The UI action I defined to do this is listed below.  As you suggested, I made use of the input element, type 'file'.

 

/**
 * Custom UI-Action 'ui-upload'.
 *
 * Description:
 * [Add description here]
 */
(function () {
    var actions = shmi.pkg("visuals.session.userActions"); //get reference to userActions object

    var _ipAddress = "192.168.1.1";
    var _username = "";   
    var _password = "";   
    var _dir = "dir=scripts";

    /**
     * UI-Action 'ui-upload' implementation
     *
     * @params {any[]} parameters  configured ui-action parameters
     * ---- Initial parameters, needs to be updated manually when changed ----
     * @param {string} parameters[0]  ipAddress
     * @param {string} parameters[1]  username
     * @param {string} parameters[2]  password
     *
     */
    actions["ui-upload"] = async function (parameters) {
        const im = shmi.requires("visuals.session.ItemManager"); //get reference to ItemManager instance

        _ipAddress = im.readValue(parameters[0]);
        _username = im.readValue(parameters[1]);
        _password = im.readValue(parameters[2]);

        let input = document.createElement('input');
        input.type = 'file';
        input.accept = '.py';
        input.multiple = true;

        input.onchange = async _ => {
            let zip = new JSZip();
            let files = Array.from(input.files);

            files.forEach(f => {
                zip.file(f.name, f.text());
            });

            await zip.generateAsync({ type: "blob" })
                .then(blob => {
                    transfer(blob);
                });
        };
        input.click();

    };

    async function transfer(blob) {

        let response = await axios.post(`https://${_ipAddress}/identity-manager/api/v2/auth/token`, { name: _username, password: _password });
        // Add error handling

        const token = response.data.access_token;

        await axios({
            method: "put",
            url: `https://${_ipAddress}/solutions/api/v1/solutions/DefaultSolution/configurations/appdata/archive?${_dir}`,
            data: blob,
            headers: { authorization: 'Bearer ' + token, "Content-Type": "application/json" },
        });
        // Add error handling

        console.log('Transfer success...');

    }

}());

 

 

 I've attached a short video clip that shows its use.  Note that I'm making use of the axios and jszip JavaScript libraries, which have to be packaged separately.  The WebIQ documentation describes how to package such libraries is great detail.

Hi @bostroemc ,

Thank you, this is very helpful. I have implemented the script and it works until I want to upload the file to the plc using the solutions API. I use exactly the same url as you, but I get an axios error with status code 400: bad request.
Do I have to set something up at the PLC side? Is DefaultSolution something that I have to setup first?
Thanks!

@suitendaal,

In the script above, the ip address and user credentials are read from WebIQ variables. Do you have these paramaters set?

Can you confirm there is a token retrieved from the ctrlX OS authentication system?

const token = response.data.access_token;

Once you have the token, you can upload files using the webdav path. For example,

https://127.0.0.1:8443/solutions/webdav/appdata/test/test.txt

Sgilk_0-1722432633132.png

 

bostroemc
Occasional Contributor

 @suitendaal: If you have not installed the Python runtime app you may not have a scripts folder in your app data.  (See the video.)  

In any case, substituting the webdav path for what I used in my sample code, as @Sgilk suggests, is an excellent idea.  

suitendaal
Established Member

Hi @Sgilk ,

I can confirm that I get a token.
Using the webdav link I get a 401 axios error.

async function transfer(blob) {

    let response = await axios.post(`https://${_ipAddress}/identity-manager/api/v2/auth/token`, { name: _username, password: _password });
    
    // Add error handling

    const token = response.data.access_token;

    await axios({
        method: "put",
        url: `https://${_ipAddress}/solutions/webdav/appdata/test/test.txt`,
        data: blob,
        headers: { authorization: 'Bearer ' + token, "Content-Type": "application/json" },
    });
    // Add error handling

    console.log('Transfer success...');

}

I am now trying to upload a .txt file and this is my transfer function.

401 is an authentication error. Does the relevent user have permission to view/manage configurations?

suitendaal
Established Member

The user has full access, so I guess yes?

Does the /test directory exist in your appdata? Try and just upload the test.txt to the appdata without the /test directory.

suitendaal
Established Member

suitendaal_0-1722435512804.png

Hi @Sgilk ,

Yes, the /appdata/test directory exists, I created it using WinSCP. Also, uploading it directly to the /appdata directory gives the same 401 error.

bostroemc
Occasional Contributor

I checked that my orignal script was working with ctrlX CORE firmware 2.6.x and WebIQ 2.15.9, but that the alternative webdav path suggested by sgilk (and seconded by me) was not.  I am not sure why.

Note that the URL does not include the file name - the blob is a temporary compressed object consisisting of the chosen files; the CORE is un-compressing these automatically.  Can you check that _dir is set correctly and re-run the original script?

.
.
.
    var _dir = "dir=test";
.
.
.

    async function transfer(blob) {

        let response = await axios.post(`https://${_ipAddress}/identity-manager/api/v2/auth/token`, { name: _username, password: _password });
        // Add error handling

        const token = response.data.access_token;

        await axios({
            method: "put",
            url: `https://${_ipAddress}/solutions/api/v1/solutions/DefaultSolution/configurations/appdata/archive?${_dir}`,
            //url: `https://${_ipAddress}/solutions/webdav/appdata?${_dir}`,
            data: blob,
            headers: { authorization: 'Bearer ' + token, "Content-Type": "application/json" },
        });
        // Add error handling

        console.log('Transfer success...');

    }


}());

 

Hi @bostroemc ,

Using your url I get a 400 (bad request) error. I get the same error using the /solutions/{solution}/configurations/{configuration}/archive test in the API reference at https://192.168.2.10/solutions/doc/api/
Using the webdav url as @Sgilk suggested I get a 401 (unauthorized) error, even though the user has full access.

@suitendaal ,

What version of ctrlX OS are you using?

suitendaal
Established Member

Hi @Sgilk ,

Do I find this under the Device Admin app? I think it is 1.20.7.

suitendaal_0-1722497002505.png

 

I tried using the YARC Chrome extension by following this how-to and tried to upload some content to https://192.168.2.10/solutions/webdav/appdata/test/test.txt. Here I am prompted by Chrome to fill in a username and password. However, it doesn't accept my credentials and if I cancel I get the authorization error 401.
I tried the authorization header with capital and small letter A and I think it is clear now that something goes wrong in the authorization, but what is it?

suitendaal_0-1722505552475.png

 

I found out that Basic authentication actually does work!

suitendaal_0-1722506912042.png

Maybe something is wrong with the token generator?

I changed the transfer method and now it is working.

async function transfer(blob) {

    await axios({
        method: "put",
        url: `https://${_ipAddress}/solutions/webdav/appdata/test/test.txt`,
        data: blob,
        headers: { "Content-Type": "application/json" },
        auth: {
            username: _username,
            password: _password,
        }
    });
    // Add error handling

    console.log('Transfer success...');

}

 

Icon--AD-black-48x48Icon--address-consumer-data-black-48x48Icon--appointment-black-48x48Icon--back-left-black-48x48Icon--calendar-black-48x48Icon--center-alignedIcon--Checkbox-checkIcon--clock-black-48x48Icon--close-black-48x48Icon--compare-black-48x48Icon--confirmation-black-48x48Icon--dealer-details-black-48x48Icon--delete-black-48x48Icon--delivery-black-48x48Icon--down-black-48x48Icon--download-black-48x48Ic-OverlayAlertIcon--externallink-black-48x48Icon-Filledforward-right_adjustedIcon--grid-view-black-48x48IC_gd_Check-Circle170821_Icons_Community170823_Bosch_Icons170823_Bosch_Icons170821_Icons_CommunityIC-logout170821_Icons_Community170825_Bosch_Icons170821_Icons_CommunityIC-shopping-cart2170821_Icons_CommunityIC-upIC_UserIcon--imageIcon--info-i-black-48x48Icon--left-alignedIcon--Less-minimize-black-48x48Icon-FilledIcon--List-Check-grennIcon--List-Check-blackIcon--List-Cross-blackIcon--list-view-mobile-black-48x48Icon--list-view-black-48x48Icon--More-Maximize-black-48x48Icon--my-product-black-48x48Icon--newsletter-black-48x48Icon--payment-black-48x48Icon--print-black-48x48Icon--promotion-black-48x48Icon--registration-black-48x48Icon--Reset-black-48x48Icon--right-alignedshare-circle1Icon--share-black-48x48Icon--shopping-bag-black-48x48Icon-shopping-cartIcon--start-play-black-48x48Icon--store-locator-black-48x48Ic-OverlayAlertIcon--summary-black-48x48tumblrIcon-FilledvineIc-OverlayAlertwhishlist