steelbrain / node-ssh

SSH2 with Promises
MIT License
947 stars 94 forks source link

When used on the renderer process of the Electron environment, the message "Class constructor NodeSSH cannot be invoked without 'new'" is displayed and instantiation fail. #427

Open TakamiChie opened 2 years ago

TakamiChie commented 2 years ago

Hello.

I am considering the use of NodeSSH in the Electron environment. I made the NodeSSH module available on the renderer process using a preload mechanism in order to reflect the processing result on the screen.

// main.js
const { app, BrowserWindow } = require('electron')
const path = require("path");

let win
function createWindow() {
  win = new BrowserWindow({
    width: 400,
    height: 400,
    webPreferences: {
      nodeIntegration: true, 
      preload: path.join(__dirname, './src/preload.js'),
      contextIsolation: true,
    }
  });
  // ...
}
app.on('ready', () => {
  createWindow();
})
// preload.js
const { contextBridge, ipcRenderer} = require("electron");
contextBridge.exposeInMainWorld(
  "requires", {
    nodessh : require('node-ssh'),
    ipcRenderer : ipcRenderer,
  }
);
// index.js(In RendererProcess)
const {NodeSSH} = window.requires.nodessh;
const ssh = new NodeSSH();
// ...

In this way, electron execution itself is possible, but on the second line of the index .js, an error of "Class constructor NodeSSH cannot be invoked without 'new'" occurs and the script stops executing.

When I checked in electron's developer console, the value of "window.requires.nodessh" seems to be "{SSHError: ƒ, NodeSSH: ƒ}", and I think the loading itself is successful, but is there any missing description?


Environment

Windows 11 Pro Version 21H2 Build: 22000.613

npm -v

8.4.1

.\node_modules.bin\electron -v

v18.1.0

steelbrain commented 2 years ago

Hello! This is quite an interesting bug you're hitting. If I had to guess, it'a a bundler messing things up somewhere.

Can you please post the output of the code below?

// index.js(In RendererProcess)
const {NodeSSH} = window.requires.nodessh;
console.log(NodeSSH.toString())
// ...
TakamiChie commented 2 years ago

Thanks for the reply. Electron's developer console displays the following message:

Uncaught Error: Class constructor NodeSSH cannot be invoked without 'new' at index.js:2:13

Also, if I check the NodeSSH value from the console, it looks like this:

NodeSSH ƒ () { [native code] }

The value of window.requires.nodessh is as follows.

{SSHError: ƒ, NodeSSH: ƒ} NodeSSH: ƒ ()arguments: nullcaller: nulllength: 0name: ""prototype: {constructor: ƒ}[Prototype]: ƒ ()[[Scopes]]: Scopes[0]SSHError: ƒ ()arguments: nullcaller: nulllength: 0name: ""prototype: {constructor: ƒ}[Prototype]: ƒ ()[[Scopes]]: Scopes[0]

Did it become a clue?

steelbrain commented 2 years ago

Aha! That explains it! It's a limitation in the Electron code proxy. You'll have to work it around.

Instead of exporting node-ssh directly, you'll have to expose a local file that acts as a proxy for the APIs for node-ssh. For example, a file that exports a function function getNodeSSH(opts) { return new NodeSSH(opts) } in preload, and then call getNodeSSH in renderer so that you do not have to use new keyword in renderer and can move forward

TakamiChie commented 2 years ago

Thanks for the reply. The code was changed as follows and the process was performed.

// preload.js
const { contextBridge, ipcRenderer} = require("electron");
const { NodeSSH } = require("node-ssh"); // changed

contextBridge.exposeInMainWorld(
  "requires", {
    getNodeSSH: function (opts) { return new NodeSSH(opts) }, // changed
    ipcRenderer : ipcRenderer,
  }
);
// index.js(In RendererProcess)
const ssh = window.requires.getNodeSSH(); // changed
ssh.connect({...}); // changed

However, the following error prevents the SSH connection.

Uncaught TypeError: ssh.connect is not a function at index.js:3:5

When I checked the ssh variable, it seems to be reading it because it says {connection: null}, but when you check ssh.connect the value is undefined.

In addition, it was confirmed that the desired processing can be executed when SSH connection is performed in the main process using ipcRenderer. When using NodeSSH, is it better to make an SSH connection in the main process using interprocess communication?