bytenode/bytenode

Question: How to create a preload.js launcher?

zrougeau opened this issue · 10 comments

Hello,

I'm trying to create a preload launcher so that I can hide and protect my code. I tried the following already with no success:

#85

How can I create a preload launcher that will execute a .jsc file?

Based on this quick start example:

// package.json
{
  "name": "preload-example",
  "version": "1.0.0",
  "description": "",
  "private": true,
  "main": "main.js",
  "scripts": {
    "start": "electron .",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "electron": "^22.0.0"
  },
  "dependencies": {
    "bytenode": "^1.3.7"
  }
}
// main.js
'use strict';

const path = require('path');
const { app, BrowserWindow } = require('electron');

console.log(__dirname);

const createWindow = () => {
  const win = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      preload: path.join(__dirname, 'preload.js'),
      nodeIntegration: true
    }
  });

  win.loadFile('index.html');
};

app.whenReady().then(() => {
  createWindow();
});
// preload.js
'use strict';

const bytenode = require('bytenode');
const path = require('path');

require('./protected.jsc')(window);
// protected.js
'use strict';

module.exports = function (window) {

  window.addEventListener('DOMContentLoaded', () => {
    const replaceText = (selector, text) => {
      const element = document.getElementById(selector);
      if (element) element.innerText = text;
    };

    for (const dependency of ['chrome', 'node', 'electron']) {
      replaceText(`${dependency}-version`, process.versions[dependency]);
    }
  });
};
<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8">
  <!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
  <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">
  <title>Hello World!</title>
</head>

<body>
  <h1>Hello World!</h1>
  We are using Node.js <span id="node-version"></span>,
  Chromium <span id="chrome-version"></span>,
  and Electron <span id="electron-version"></span>.
</body>

</html>

Now, compile your protected.js file as follows:

$ npm i bytenode

$ ELECTRON_RUN_AS_NODE=1 ./node_modules/.bin/electron ./node_modules/bytenode/lib/cli.js -c protected.js

Run your app as usual:

$ npm run start

I couldn't get it to run, I get this error:

The term 'ELECTRON_RUN_AS_NODE=1' is not recognized as the name of a cmdlet, function, script file, or operable program.

Are you on a Windows machine?

Try these commands:

set ELECTRON_RUN_AS_NODE=true

./node_modules/.bin/electron ./node_modules/bytenode/lib/cli.js -c protected.js

Are you on a Windows machine?

Try these commands:

set ELECTRON_RUN_AS_NODE=true

./node_modules/.bin/electron ./node_modules/bytenode/lib/cli.js -c protected.js

Thanks, I've tried it and I still get this issue when I do npm run make.

I'm trying to use ipcRenderer and contextBridge in my protected.jsc file. I keep getting this error:

Module parse failed: Unexpected character '♣' (1:1)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
(Source code omitted for this binary file)

Yes, I'm using a windows machine.

My simple project demonstrates how to run a protected .jsc file from the preload.js file. Nothing more, nothing less. It does NOT include an npm run make step at all.

Create a minimal example that shows your error that I can reproduce and test. I can't help you without a concrete and minimal example.

My simple project demonstrates how to run a protected .jsc file from the preload.js file. Nothing more, nothing less. It does NOT include an npm run make step at all.

Create a minimal example that shows your error that I can reproduce and test. I can't help you without a concrete and minimal example.

Sure, I can do that and thanks for helping me out. Here's what I'm trying to do:

preload.js:

use strict;
const bytenode = require('bytenode');
require('./protected.jsc')

protected.js:

const { ipcRenderer, contextBridge} = require("electron");

contextBridge.exposeInMainWorld("electron",{

openFileExplorer: () => ipcRenderer.invoke('openFileExplorer'),
openFolder: (path) => ipcRenderer.invoke('openFolder',path),

});'

I want to use the above methods from protected.js(protected.jsc once its encoded) and use them in main.js process:
const { app, BrowserWindow,dialog,ipcMain,shell,powerSaveBlocker,Menu} = require('electron');

ipcMain.handle("openFileExplorer",async(event)=>{
dialog.showOpenDialog();
return;
});

ipcMain.handle("openFolder",async(event,path)=>{
let window = BrowserWindow.getFocusedWindow();
await dialog.showOpenDialog(window,{
title:'Choose Backup Working Directory',
properties: ['openFile,multiSelections,openDirectory'],
defaultPath:path.replaceAll('/','\'),
})
});

Webpack.main.config.js:

const { BytenodeWebpackPlugin } = require('@herberttn/bytenode-webpack-plugin')
module.exports = {
/**

  • This is the main entry point for your application, it's the first file
  • that runs in the main process.
    */
    entry: {
    index: './src/main.js',
    },
    output: {
    filename: '[name].js',
    },
    target: 'electron-main',

module: {
rules: require('./webpack.rules'),
},
plugins: [
// using all defaults
new BytenodeWebpackPlugin(),

// overriding an option
new BytenodeWebpackPlugin({
  compileForElectron: true,
}),

],

webpack.renderer.config:

const rules = require('./webpack.rules');

rules.push({
test: /.css$/,
use: [{ loader: 'style-loader' }, { loader: 'css-loader' }],
});

module.exports = {
// Put your normal webpack config below here
module: {
rules,
},
};

main.js:

const { app, BrowserWindow,dialog,ipcMain,shell,powerSaveBlocker,Menu} = require('electron');
const createWindow = () => {
// Create the browser window.
mainWindow = new BrowserWindow({
width: 800,
height: 600,
sandbox:false,
webPreferences: {
contextIsolation: true,
webSecurity:false,
preload: __dirname+"/preload.js",
},
});

app.on('activate', () => {
// On OS X it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});

I will use my minimal example to achieve your goal. Please study the code below carefully to understand how to tweak it to fit your app:

// preload.js
'use strict';

const bytenode = require('bytenode');
const path = require('path');

const { ipcRenderer, contextBridge} = require('electron');

require('./protected.jsc')(window, ipcRenderer, contextBridge);

See? we required both ipcRenderer, contextBridge from the preload.js file itself. This is the un-protected file.

// protected.js
'use strict';

module.exports = function (window, ipcRenderer, contextBridge) {

  contextBridge.exposeInMainWorld('electron', {
    openFileExplorer: () => ipcRenderer.invoke('openFileExplorer'),
    openFolder: (path) => ipcRenderer.invoke('openFolder', path),
  });

  window.addEventListener('DOMContentLoaded', () => {
    const replaceText = (selector, text) => {
      const element = document.getElementById(selector);
      if (element) element.innerText = text;
    };

    for (const dependency of ['chrome', 'node', 'electron']) {
      replaceText(`${dependency}-version`, process.versions[dependency]);
    }
  });
};

Here, we converted the whole protected.js file to be a function that is exported to the module.exports object. We give this function the dependencies that we need. In our case, the deps are window, ipcRenderer, contextBridge. Then, inside the function, we define our contextBridge.exposeInMainWorld logic.

// main.js

/**
 * The same content as the previous main.js file. Then, add the following code:
**/

ipcMain.handle('openFileExplorer', async (event) => {
  console.log('openFileExplorer event received.');
  return;
});

ipcMain.handle('openFolder', async (event, path) => {
  console.log(`openFolder event received, with path = ${path}.`);
});

Finally, I have compiled the protected.js file to protected.jsc as mentioned before:

In Linux, use this command:

$ ELECTRON_RUN_AS_NODE=1 ./node_modules/.bin/electron ./node_modules/bytenode/lib/cli.js -c protected.js

In Windows, use these two commands:

$ set ELECTRON_RUN_AS_NODE=true

$ ./node_modules/.bin/electron ./node_modules/bytenode/lib/cli.js -c protected.js

Then, run the example app:

$ npm run start

Now, you can open the devtools and go to the console. From there, you will find electron object exposed as expected, where you can use it to send messages to the main.js file.

image

Thank you for your help, I really appreciate it. I got it working :)

The main.js must require the bytenode, otherwise the preload.js cannot require ('bytenode').