herberttn / bytenode-webpack-plugin

Compile JavaScript into bytecode using bytenode
MIT License
105 stars 27 forks source link

"unable to load preload script" error when changing contextIsolation to true and nodeIntegration to false #30

Closed raphael10-collab closed 9 months ago

raphael10-collab commented 1 year ago

I git cloned and started https://github.com/herberttn/bytenode-webpack-plugin/tree/main/examples/electron-forge-typescript-webpack

I tried to change the contextIsolation and the nodeIntegration settings respectively to true and false :

const createWindow = (): void => {
  // Create the browser window.
  const mainWindow = new BrowserWindow({
    height: 600,
    width: 800,
    webPreferences: {
      //contextIsolation: false,
      //nodeIntegration: true,
      //preload: MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY,

      preload: MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY,
      nodeIntegration: false,
      contextIsolation: true,
      nodeIntegrationInWorker: false,
      nodeIntegrationInSubFrames: false,
      webviewTag: true,
      webSecurity: true

    },
  });

  // and load the index.html of the app.
  void mainWindow.loadURL(MAIN_WINDOW_WEBPACK_ENTRY);

with preload.ts :

console.log('💃 hello from preload')

// https://www.electronjs.org/docs/latest/tutorial/ipc

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

export {}
declare global {
  interface Window {
    api: {
      send: (channel: string, ...arg: any) => void;
      receive: (channel: string, func: (event: any, ...arg: any) => void) => void;
      electronIpcSendTo: (window_id: string, channel: string, ...arg: any) => void;
      electronIpcSend: (channel: string, ...arg: any) => void;
      giveMeAStream: (eventId: string) => void;
      electronIpcOn: (channel: string, listener: (event: any, ...arg: any) => void) => void;
      electronIpcSendSync: (channel: string, ...arg: any) => void;
      electronIpcInvoke: (channel: string, ...arg: any) => void;
      electronIpcPostMessage: (channel: string, message: any, transfer?: MessagePort[]) => void;
      electronIpcOnce: (channel: string, listener: (event: any, ...arg: any) => void) => void;
      electronIpcRemoveListener:  (channel: string, listener: (event: any, ...arg: any) => void) => void;
      electronIpcRemoveAllListeners: (channel: string) => void;

      setFullscreen: (flag: any) => void;

    };

    attachEvent(event: string, listener: EventListener): boolean;
    detachEvent(event: string, listener: EventListener): void;

    // https://www.electronjs.org/docs/latest/tutorial/context-isolation#usage-with-typescript

    bwScrollAPI: {
      getScrollAmount: () => void;
    }
  }
}

contextBridge.exposeInMainWorld(
  "api", {

      electronIpcPostMessage: (channel: string, message: any, transfer?: MessagePort[]) => {
        ipcRenderer.postMessage(channel, message, transfer)
      },
      send: (channel: any, data: any) => {
          console.log("preload-send called: args: ", data);
          ipcRenderer.invoke(channel, data).catch(e => console.log(e))
      },
      receive: (channel: any, func: any) => {
        console.log("preload-receive called. args: ");
        ipcRenderer.on(channel, (event, ...args) => func(...args));
      },

      electronIpcSendTo: (window_id: number, channel: string, ...arg: any) => {
        ipcRenderer.sendTo(window_id, channel, arg);
      },

      electronIpcSend: (channel: string, ...arg: any) => {
        ipcRenderer.send(channel, arg);
      },

      giveMeAStream: (eventId: string) => {
        ipcRenderer.send('give-me-a-stream', eventId)
      },

      electronIpcSendSync: (channel: string, ...arg: any) => {
        return ipcRenderer.sendSync(channel, arg);
      },
      // https://www.electronjs.org/docs/latest/api/ipc-renderer#ipcrendererinvokechannel-args
      electronIpcInvoke: (channel: string, ...arg: any) => {
        return ipcRenderer.invoke(channel, ...arg)
      },
      electronIpcOn: (channel: string, listener: (event: any, ...arg: any) => void) => {
        ipcRenderer.on(channel, listener);
      },
      electronIpcOnce: (channel: string, listener: (event: any, ...arg: any) => void) => {
        ipcRenderer.once(channel, listener);
      },
      electronIpcRemoveListener:  (channel: string, listener: (event: any, ...arg: any) => void) => {
        ipcRenderer.removeListener(channel, listener);
      },
      electronIpcRemoveAllListeners: (channel: string) => {
        ipcRenderer.removeAllListeners(channel);
      },

      setFullscreen: (flag: string) => {
        ipcRenderer.invoke('setFullscreen', flag);
      },

  },
)

But, after this change, I get this error: unable to load preload script

image

what am I missing ? How to make it work?

raphael10-collab commented 1 year ago

Hi @jjeff !

In this repo: https://github.com/raphael10-collab/bytenode-contextIsolationTrue-electron-forge-typescript-webpack you can see that I've set nodeIntegration to false, and contextIsolation to true

As you explained here: https://github.com/herberttn/bytenode-webpack-plugin/issues/29#issuecomment-1702995183 Bytenode always require Node to work.

If I want to keep for good electron safety practice - https://www.electronjs.org/docs/latest/tutorial/security - nodeIntegration to false, and contextIsolation to true, you suggest me to have all Bytenode compiled code in the preload scripts, because preload scripts have access to the renderer scope.

I've set up my preload.ts script as follows:

https://github.com/raphael10-collab/bytenode-contextIsolationTrue-electron-forge-typescript-webpack/blob/master/src/main/preload/preload.ts :

console.log('💃 hello from preload')

// https://www.electronjs.org/docs/latest/tutorial/ipc

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

export {}
declare global {
  interface Window {
    //setupRenderer,
    api: {
      send: (channel: string, ...arg: any) => void;
      receive: (channel: string, func: (event: any, ...arg: any) => void) => void;
      // https://github.com/frederiksen/angular-electron-boilerplate/blob/master/src/preload/preload.ts
      // https://www.electronjs.org/docs/all#ipcrenderersendtowebcontentsid-channel-args
      electronIpcSendTo: (window_id: string, channel: string, ...arg: any) => void;
      electronIpcSend: (channel: string, ...arg: any) => void;
      giveMeAStream: (eventId: string) => void;
      electronIpcOn: (channel: string, listener: (event: any, ...arg: any) => void) => void;
      electronIpcSendSync: (channel: string, ...arg: any) => void;
      // https://www.electronjs.org/docs/latest/api/ipc-renderer#ipcrendererinvokechannel-args
      electronIpcInvoke: (channel: string, ...arg: any) => void;
      // https://www.electronjs.org/docs/latest/api/ipc-renderer#ipcrendererpostmessagechannel-message-transfer
      electronIpcPostMessage: (channel: string, message: any, transfer?: MessagePort[]) => void;
      electronIpcOnce: (channel: string, listener: (event: any, ...arg: any) => void) => void;
      electronIpcRemoveListener:  (channel: string, listener: (event: any, ...arg: any) => void) => void;
      electronIpcRemoveAllListeners: (channel: string) => void;

      setFullscreen: (flag: any) => void;

    };

    attachEvent(event: string, listener: EventListener): boolean;
    detachEvent(event: string, listener: EventListener): void;

    // https://www.electronjs.org/docs/latest/tutorial/context-isolation#usage-with-typescript

    bwScrollAPI: {
      getScrollAmount: () => void;
    }
  }
}

contextBridge.exposeInMainWorld(
  "api", {
      // https://www.electronjs.org/docs/latest/api/ipc-renderer#ipcrendererpostmessagechannel-message-transfer
      electronIpcPostMessage: (channel: string, message: any, transfer?: MessagePort[]) => {
        ipcRenderer.postMessage(channel, message, transfer)
      },
      send: (channel: any, data: any) => {
          console.log("preload-send called: args: ", data);
          ipcRenderer.invoke(channel, data).catch(e => console.log(e))
      },
      receive: (channel: any, func: any) => {
        console.log("preload-receive called. args: ");
        ipcRenderer.on(channel, (event, ...args) => func(...args));
      },
      // https://www.electronjs.org/docs/all#ipcrenderersendtowebcontentsid-channel-args
      electronIpcSendTo: (window_id: number, channel: string, ...arg: any) => {
        ipcRenderer.sendTo(window_id, channel, arg);
      },
      // https://github.com/frederiksen/angular-electron-boilerplate/blob/master/src/preload/preload.ts
      electronIpcSend: (channel: string, ...arg: any) => {
        ipcRenderer.send(channel, arg);
      },

      giveMeAStream: (eventId: string) => {
        ipcRenderer.send('give-me-a-stream', eventId)
      },

      electronIpcSendSync: (channel: string, ...arg: any) => {
        return ipcRenderer.sendSync(channel, arg);
      },
      // https://www.electronjs.org/docs/latest/api/ipc-renderer#ipcrendererinvokechannel-args
      electronIpcInvoke: (channel: string, ...arg: any) => {
        return ipcRenderer.invoke(channel, ...arg)
      },
      electronIpcOn: (channel: string, listener: (event: any, ...arg: any) => void) => {
        ipcRenderer.on(channel, listener);
      },
      electronIpcOnce: (channel: string, listener: (event: any, ...arg: any) => void) => {
        ipcRenderer.once(channel, listener);
      },
      electronIpcRemoveListener:  (channel: string, listener: (event: any, ...arg: any) => void) => {
        ipcRenderer.removeListener(channel, listener);
      },
      electronIpcRemoveAllListeners: (channel: string) => {
        ipcRenderer.removeAllListeners(channel);
      },

      setFullscreen: (flag: string) => {
        ipcRenderer.invoke('setFullscreen', flag);
      },

  },
)

What should I practically add to the above preload.ts code in order to have my all Bytonode compiled code within preload.ts ?

jjeff commented 1 year ago

You might want to play with https://github.com/spaceagetv/electron-bytenode-example and try different configurations in a simplified environment. I have yet to implement Electron security best practices in that repo, but I think it can be done. In most of my applications, I'm not loading untrusted web pages into my renderers, so I'm okay without context isolation enabled.

What should I practically add to the above preload.ts code in order to have my all Bytonode compiled code within preload.ts ?

I don't know if there's anything you need to add to the preload script to compile it with Bytenode. You just need to configure webpack to compile preload scripts.

If you're asking if it's possible to move your renderer script code into your preload script so that it will be compiled, the answer is usually "yes". The main trick is to use the DOMContentLoaded event to keep your DOM-interacting code from running before the DOM has been loaded.

raphael10-collab commented 1 year ago

I've set nodeIntegration to false and contextIsolation to true

And

I've configured webpack to compile preload scripts, I've set the plugins in webpack config, specifying the compileForElectron flag, and I've tried to put inside the preload this example code: https://github.com/spaceagetv/electron-bytenode-example/blob/main/src/renderer/preload.ts.

But it does not work

I've updated the repo in github: https://github.com/raphael10-collab/bytenode-contextIsolationTrue-electron-forge-typescript-webpack.git to contain all these aforementioned trials

So, I guess there is something missing, in order to make bytenode work with the Electron security best practices in place, with nodeIntegration to false and contextIsolation to true as fixed landmarks

raphael10-collab commented 1 year ago

@jjeff

setting :

  contextIsolation: false,
  nodeIntegration: true,
  sandbox: false,

yields this error : Cannot find module `v8`
image

I've updated the repo in github: https://github.com/raphael10-collab/bytenode-contextIsolationTrue-electron-forge-typescript-webpack.git to contain all these aforementioned trials

The best would be to be able to set

contextIsolation: true,
nodeIntegration: false,

while being able to use bytenode-webplack-plugin

PascalPixel commented 1 year ago

Just my two cents; I've spent a few weeks this year (yes... full time 😅) trying to get bytenode to work with nodeIntegration off, but was unable to get it to work.

What did work was to make a fork of bytenode that strips the 'encoding' bits from the library, and only leave the decoding bits.

This is done in electron-vite as well, and in the end I migrated from webpack to vite so as to not manage all this myself.

herberttn commented 9 months ago

Duplicate of #28