0

I'm trying to get an electron app running with typescript, where i'm able to structure my code. The most useful starting point in the was the first answer in this question: https://stackoverflow.com/a/71078436/3735510

However, i'm unable to extend my app, there's no error reported from typescript but during runtime i receive an Uncaught ReferenceError and the call doesn't work.

This is my folder & file structure:

├── forge.config.ts
├── node_modules (...)
├── package-lock.json
├── package.json
├── src
│   ├── auth
│   │   ├── index.ts
│   │   └── loginhandling.ts
│   ├── index.css
│   ├── index.d.ts
│   ├── index.html
│   ├── index.ts
│   ├── preload.ts
│   └── renderer.ts
├── tsconfig.json
├── webpack.main.config.ts
├── webpack.plugins.ts
├── webpack.renderer.config.ts
├── webpack.rules.ts
└── yarn.lock

This is the forge.config.ts:

import { MakerSquirrel } from '@electron-forge/maker-squirrel';
import { MakerZIP } from '@electron-forge/maker-zip';
import { MakerDeb } from '@electron-forge/maker-deb';
import { MakerRpm } from '@electron-forge/maker-rpm';
import { WebpackPlugin } from '@electron-forge/plugin-webpack';

import { mainConfig } from './webpack.main.config';
import { rendererConfig } from './webpack.renderer.config';

const config: ForgeConfig = {
  packagerConfig: {},
  rebuildConfig: {},
  makers: [new MakerSquirrel({}), new MakerZIP({}, ['darwin']), new MakerRpm({}), new MakerDeb({})],
  plugins: [
    new WebpackPlugin({
      mainConfig,
      renderer: {
        config: rendererConfig,
        entryPoints: [
          {
            html: './src/index.html',
            js: './src/renderer.ts',
            name: 'main_window',
            preload: {
              js: './src/preload.ts',
            },
          },
        ],
      },
    }),
  ],
};

export default config;

my package.json:

  "name": "someName",
  "productName": "someName",
  "version": "1.0.0",
  "description": "My Electron application description",
  "main": ".webpack/main",
  "scripts": {
    "start": "electron-forge start",
    "package": "electron-forge package",
    "make": "electron-forge make",
    "publish": "electron-forge publish",
    "lint": "eslint --ext .ts,.tsx ."
  },
  "keywords": [],
  "author": {
    "name": "A Name",
    "email": "someone@this.org"
  },
  "license": "MIT",
  "devDependencies": {
    "@electron-forge/cli": "^6.0.4",
    "@electron-forge/maker-deb": "^6.0.4",
    "@electron-forge/maker-rpm": "^6.0.4",
    "@electron-forge/maker-squirrel": "^6.0.4",
    "@electron-forge/maker-zip": "^6.0.4",
    "@electron-forge/plugin-webpack": "^6.0.4",
    "@typescript-eslint/eslint-plugin": "^5.0.0",
    "@typescript-eslint/parser": "^5.0.0",
    "@vercel/webpack-asset-relocator-loader": "1.7.3",
    "css-loader": "^6.0.0",
    "electron": "22.1.0",
    "eslint": "^8.0.1",
    "eslint-plugin-import": "^2.25.0",
    "fork-ts-checker-webpack-plugin": "^7.2.13",
    "node-loader": "^2.0.0",
    "style-loader": "^3.0.0",
    "ts-loader": "^9.2.2",
    "ts-node": "^10.0.0",
    "typescript": "~4.5.4"
  },
  "dependencies": {
    "electron-squirrel-startup": "^1.0.0"
  },
  "entryPoints": {
    "preload": {
      "js": "./src/preload.ts"
    }
  }
}

My tsconfig.json:

  "compilerOptions": {
    "target": "ES6",
    "allowJs": true,
    "module": "commonjs",
    "skipLibCheck": true,
    "esModuleInterop": true,
    "noImplicitAny": true,
    "sourceMap": true,
    "baseUrl": ".",
    "outDir": "dist",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "paths": {
      "*": ["node_modules/*"]
    }
  },
  "include": ["src/**/*"],
  "files": [
    "src/index.d.ts"
  ]
}

auth/index.ts:


// api exports functions that make up the frontend api, ie that in
// turn either do IPC calls to main for db communication or use
// allowed nodejs features like file i/o.
// Example `my-feature.ts`: 
// export const fetchX = async (): Promise<X> => { ... }

export default {
    ...loginhandling
}

and auth/loginhandling.ts:

export const login = async (): Promise<boolean> => { 
    console.log("Triggered login"); 
    return true;
}

export const refresh = async (): Promise<boolean> => { 
    console.log("Triggered refresh"); 
    return true;
}


export const buttonCLicked = async (): Promise<void> => {
    console.log(`button clicked, let's do something!`);
}

Here is my index.html:

<html>
  <head>
    <meta charset="UTF-8" />
    <title>Hello World!</title>

  </head>
  <body>
    <h1> Hello World!</h1>
    <p>Welcome to your Electron application.</p>
    <p>
      <button class="button" id="loginButton">Login</button>
      <label class="buttonDescription" id="loginButtonDescription">Login details...</label>
    </p>
  </body>
</html>

Here is my index.ts:

// This allows TypeScript to pick up the magic constants that's auto-generated by Forge's Webpack
// plugin that tells the Electron app where to look for the Webpack-bundled app code (depending on
// whether you're running in development or production).
declare const MAIN_WINDOW_WEBPACK_ENTRY: string;
declare const MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY: string;

// Handle creating/removing shortcuts on Windows when installing/uninstalling.
if (require('electron-squirrel-startup')) {
  app.quit();
}

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

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

  // Open the DevTools.
  // mainWindow.webContents.openDevTools();
};

// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', createWindow);

// Quit when all windows are closed, except on macOS. There, it's common
// for applications and their menu bar to stay active until the user quits
// explicitly with Cmd + Q.
app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') {
    app.quit();
  }
});

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();
  }
});

// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and import them here.

the index.d.ts:

declare const auth: typeof import("./auth").default;

my preload.ts:

// https://www.electronjs.org/docs/latest/tutorial/process-model#preload-scripts

import { contextBridge } from "electron";
import auth from './auth'

contextBridge.exposeInMainWorld("auth", auth);

and the (for me problematic) renderer.ts, currently with two listeners for the same event:

 * This file will automatically be loaded by webpack and run in the "renderer" context.
 * To learn more about the differences between the "main" and the "renderer" context in
 * Electron, visit:
 *
 * https://electronjs.org/docs/latest/tutorial/process-model
 *
 * By default, Node.js integration in this file is disabled. When enabling Node.js integration
 * in a renderer process, please be aware of potential security implications. You can read
 * more about security risks here:
 *
 * https://electronjs.org/docs/tutorial/security
 *
 * To enable Node.js integration in this file, open up `main.js` and enable the `nodeIntegration`
 * flag:
 *
 * ```
 *  // Create the browser window.
 *  mainWindow = new BrowserWindow({
 *    width: 800,
 *    height: 600,
 *    webPreferences: {
 *      nodeIntegration: true
 *    }
 *  });
 * ```
 */

import './index.css';

console.log(' This message is being logged by "renderer.ts", included via webpack');
// auth.login();

// get UI elements and connect
// get button
const loginBttn = <HTMLButtonElement>document.getElementById('loginButton');
// get label
const loginLbl = <HTMLLabelElement>document.getElementById('loginButtonDescription');
loginBttn.addEventListener('click', () => auth.buttonCLicked());

let counter = 0;
loginBttn.addEventListener('click', () => {loginLbl.innerText = `Loging clicked ${++counter} times`});

So the event listener changing the label directly does work, the other one just shows as an error: enter image description here

The question: How can i get the button click to call the code from auth/loginhandling? To my understanding this is the first step towards other parts of code / long running background tasks that i later want to trigger from buttons in the GUI.

trowek
  • 87
  • 7

1 Answers1

0

Try changing following :

loginBttn.addEventListener('click', () => auth.buttonCLicked());

With :

loginBttn.addEventListener('click', () => window.auth.buttonCLicked());
Wooly
  • 1