I want to store images on the users computer, so I figure it should be stored in users data folder, as described here.
app.getPath(name)
name. Returns String - A path to a special directory or file associated with name. On failure an Error is thrown. You can request the following paths by the name:
home User's home directory
appData Per-user application data directory, which by default points to:
%APPDATA% on Windows
$XDG_CONFIG_HOME or ~/.config on Linux
~/Library/Application Support on macOS
userData The directory for storing your app's configuration files, which by default it is the appData directory appended with your app's name.
...
This is what I think you're supposed to do:
const app = require('electron');
alert(app.getPath('userData'));
But I get "getPath is not a function". I am not sure where to put it. It does not work from my html file or the renderer file, and I'm not sure how to use it from the main file because that's not linked to the web page.
Since the remote method is being considered deprecated, as shown here, I'd suggest you do this:
const {app} = require('electron');
console.log(app.getPath('userData'));
remote is considered dangerous.
app.getPath will be always available in main process.
Here is how to do it in renderer process without using remote (electron 7+)
Basically you have to use invoke in renderer.
in main
ipcMain.handle('read-user-data', async (event, fileName) => {
const path = electron.app.getPath('userData');
const buf = await fs.promises.readFile(`${path}/${fileName}`));
return buf;
})
in renderer
ipcRenderer.invoke('read-user-data', 'fileName.txt').then(
result => doSomething()
);
Here is what I use when I need to switch between dev and release
const electron = require('electron');
export const userDataPath = (electron.app || electron.remote.app).getPath(
'userData'
);
Another way to prevent the error "getPath is not a function" is to make the code work both in the renderer process and the main process:
const electron = require('electron');
const configDir = (electron.app || electron.remote.app).getPath('userData');
console.log(configDir);
I had trouble with app.getPath('userData') to save/load config files, etc and ended up using OS specific env vars in the meantime:
const getAppBasePath = () => {
//dev
if (process.env.RUN_ENV === 'development') return './'
if (!process.platform || !['win32', 'darwin'].includes(process.platform)) {
console.error(`Unsupported OS: ${process.platform}`)
return './'
}
//prod
if (process.platform === 'darwin') {
console.log('Mac OS detected')
return `/Users/${process.env.USER}/Library/Application\ Support/${YOUR_APP_NAME}/`
} else if (process.platform === 'win32') {
console.log('Windows OS detected')
return `${process.env.LOCALAPPDATA}\\${YOUR_APP_NAME}\\`
}
}
if you wanna do it in renderer process
try this,it is work for me
// main.js
const electron = require('electron')
const electronRemote = process.type === 'browser' ? electron :
require('#electron/remote')
const { app, ipcMain, Menu, globalShortcut } = require('electron')
const BrowserWindow = electronRemote.BrowserWindow
const isDev= require('electron-is-dev')
const { initialize, enable } = require('#electron/remote/main')
initialize()
let mainWindow
app.on('ready', ()=>{
mainWindow = new BrowserWindow({
width: 1024,
height: 600,
minWidth:600,
webPreferences: {
nodeIntegration: true,
enableRemoteModule: true,
contextIsolation: false
}
})
enable(mainWindow.webContents)
})
render process
// render process
const { app } = window.require('#electron/remote')
const savedPath = app.getPath('userData')
Related
I am trying to open a dialog box in my electron app but when I Try importing it in my app.js the error in the title shows up even thought 'enableRemoteModule' is set to true .
I am trying to open a dialog box in my electron app but when I Try importing it in my app.js the error in the title shows up even thought 'enableRemoteModule' is set to true .
app.js
const { dialog } = require("electron").remote;
const OpenBookBtn = document.getElementById("OpenBookBtn")
const viewerElement = document.getElementById('viewer')
const CloseBookBtn = document.getElementById("CloseBookBtn")
WebViewer({
path:'../public/lib',
},viewerElement).then(instance =>{
instance.setTheme("dark");
})
viewerElement.style.display = 'none'
OpenBookBtn.addEventListener('click',()=>{
const file = dialog.showOpenDialog();
viewerElement.style.display = "block"
})
CloseBookBtn.addEventListener('click',(event)=>{
viewerElement.style.display = 'none'
})
index.js
const { app, BrowserWindow } = require('electron');
const path = require('path');
// Handle creating/removing shortcuts on Windows when installing/uninstalling.
if (require('electron-squirrel-startup')) { // eslint-disable-line global-require
app.quit();
}
const createWindow = () => {
// Create the browser window.
const mainWindow = new BrowserWindow({
webPreferences: {
nodeIntegration: true,
enableRemoteModule: true,
contextIsolation: false,
},
width: 800,
height: 600,
});
// and load the index.html of the app.
mainWindow.loadFile(path.join(__dirname, 'index.html'));
// 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.
First install the remote module using
> npm install #electron/remote
In the new versions of electron, you have to include remote as
const remote = require('#electron/remote');
const { dialog } = remote;
And in your index.js file add the following
const remoteMain = require('#electron/remote/main');
remoteMain.initialize();
Also, after creating the mainWindow object
remoteMain.enable(mainWindow.webContents)
I am trying to separate the IPC function from the main.js file in electron because it gets too long
how can I use this webContents.send in different js file not in main.js
mainWindow.webContents.send("search",recordset.recordset)
it shows this error
Cannot read properties of undefined (reading 'webContents')
Separation of concerns will be your number one priority here. To achieve this, you can use setters and getters.
Remember, when Node first require's a module, it is also cached. Let's use this advantage as a form of state management.
Prior to separating / refactoring your code, you will find that your main.js file can grow to an enormous size. Using techniques such as this will allow you to split up your code into easily manageable, readable, single responsibility segments of code.
If you haven’t done so already, let's move construction of your mainWindow out of the main.js file and into its own file.
main.js (main thread)
// Import the necessary Electron modules.
const electronApp = require('electron').app;
const electronBrowserWindow = require('electron').BrowserWindow;
// Import the necessary Node modules.
const nodePath = require('path');
// Import the necessary Application modules.
const appMainWindow = require(nodePath.join(__dirname, 'main-window'));
// Prevent garbage collection.
let mainWindow = null;
// Application is now ready to start.
electronApp.on('ready', () => {
mainWindow = appMainWindow.create();
});
// Re-activate Application when in macOS dock.
electronApp.on('activate', () => {
if (electronBrowserWindow.getAllWindows().length === 0) {
appMainWindow.create(mainWindow);
}
});
// Close Application completely if not on macOS.
electronApp.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
electronApp.quit();
}
});
Having management of the mainWindow object in its own file separates our code into more readable and manageable chunks.
main-window.js (main thread)
// Import the necessary Electron modules.
const electronBrowserWindow = require('electron').BrowserWindow;
// Define the main window.
let mainWindow;
// Create the main window.
function create() {
mainWindow = new electronBrowserWindow({
x: 0,
y: 0,
width: 800,
height: 600,
show: false,
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
preload: nodePath.join(__dirname, 'preload.js')
}
});
// Load the main window.
mainWindow.loadFile(nodePath.join(__dirname, 'main-window.html'))
.then(() => { mainWindow.show(); })
return mainWindow;
}
// Get the main window object.
function get() {
return mainWindow;
}
// Export the publicly available functions.
module.exports = {create, get};
Finally, in your file (or any other file) that requires reference to your mainWindow object, just require your main-window.js file and call the get() function.
any-file (main thread)
// Import the necessary Node modules.
const nodePath = require('path');
// Import the necessary Application modules.
const appMainWindow = require(nodePath.join(__dirname, 'main-window'));
// Lets use it.
appMainWindow.get().webContents.send("search", recordset.recordset);
When I update an Electron app using the electron-builder autoUpdater, all the file storage I defined is overwritten. What settings do I need to make these files persist?
Here is an MCVE example (for the main process):
const { app, BrowserView, BrowserWindow,ipcMain } = require('electron');
const log = require("electron-log");
const { autoUpdater } = require("electron-updater");
const fs = require( 'fs');
const path = require('path');
let win
let rand = String(Math.floor(Math.random() * 10000));
console.log('Generated random number',rand)
app.whenReady().then(async () => {
win = new BrowserWindow({
fullscreen: false,
webPreferences: {
nodeIntegration: false,
preload: path.join(__dirname, 'preload.js')
}
})
win.loadFile('index.html');
setInterval(function() {
let a = autoUpdater.checkForUpdatesAndNotify();
}, 60000);
let exists = false;
fs.stat('persistentFile',(err, stats) => {
if (err == null){
exists = true
}
if (!exists){
fs.writeFile('persistentFile',rand,(err)=>{
if (err) throw err;
win.webContents.send('console_message', 'Persistent file has been created!',rand);
console.log('Persistent file has been created!',rand);
})
}
else {
fs.readFile('persistentFile',(err,data)=>{
if (err) throw err;
win.webContents.send('console_message', 'Persistent already exists',data);
console.log('Persistent already exists',data);
})
}
})
win.webContents.send('console_message', 'Random No is' + String(rand));
})
In this example I'd like the file persistentFile to persist across updates, so that it contains the number generated from the first version. However, at the moment, the file is overwritten every update.
How would you ensure persistent file storage across electron-builder autoupdates?
I managed to do this after looking at Cameron Nokes' excellent blog:
cameronnokes.com/blog/how-to-store-user-data-in-electron:
Storing user data in the operating system’s designated location for
user’s app data is the idiomatic way for native app’s to persist user
data because:
when we auto-update the app, our source files may get moved or delete
changing or adding to an app’s internal files will invalidate the code
signature
To do this I used const userDataPath = app.getPath('userData'); to get a path to the OS' app storage location. and then changed the references to 'persistentFile' to String(userDataPath)+'/persistentFile':
I'm quite new to ElectronJS and much of my learning has come from examples and docs since it seems many tutorials out there are outdated especially due to the addition of enforcing nodeIntegration: false and contextIsolation: true.
My research has led me down the path of preload scripts, which by themselves still open themselves up to vulnerabilities, which then led me to contextBridge via preload scripts.
As a disclaimer, what I'm doing is probably unnecessary since I'm not actually connecting to any third-party and everything is local, so theoretically I could edit the webPreferences for nodeIntegration and contextIsolation. However, for my own learning purposes as well as best practice, I felt it necessary to understand how to do what I want to do and expose APIs such as require, fs, path, os etc. without using a "deprecated" feature.
Mainly wanted to post here to see if this is the proper method of using contextBridge.
main.js
const { app, BrowserWindow, Menu, ipcMain } = require("electron");
const path = require("path");
const os = require("os");
...
let mainWindow;
function createMainWindow() {
mainWindow = new BrowserWindow({
title: "Test",
icon: `${__dirname}/assets/icons/Icon_256x256.png`,
width: isDev ? 800 : 500,
height: 600,
resizable: isDev,
backgroundColor: "white",
webPreferences: {
preload: `${__dirname}/preload.js`,
nodeIntegration: false,
contextIsolation: true,
},
});
...
ipcMain.handle("get-file-path", async (event) => {
const filePath = path.join(os.homedir(), "test");
return filePath;
});
...
preload.js
const { ipcRenderer, contextBridge } = require("electron");
contextBridge.exposeInMainWorld("api", {
getFilePath: async () => {
const res = await ipcRenderer.invoke("get-file-path");
return res;
},
});
renderer.js
window.addEventListener("DOMContentLoaded", async () => {
document.getElementById("output-path").innerText =
await window.api.getFilePath();
});
It works as intended. From my understanding of what is happening here is by using contextBridge with a callback method I'm essentially keeping everything isolated within that function and not exposing anymore that what's within that closure--is that correct?
Once again, I'm really new (1 day into electron essentially). I've only been 'developing' for 7 months or so.
Would this also be valid.. offloading to another js file and exposing those functions through context bridge?
test.js
const path = require("path");
const os = require("os");
const getFilePath = () => {
const filePath = path.join(os.homedir(), "test");
return filePath;
};
module.exports = {
path: getFilePath,
};
preload.js
const { contextBridge } = require("electron");
const mainFunctions = require("./test.js");
const { path } = mainFunctions;
contextBridge.exposeInMainWorld("api", {
getFilePath: path(),
});
Both give me the outcome I expect.
I recently went back to an old project and updated my electron version. I found online that electron now requires you to add in nodeIntegration: true in order to be able to import electron in your render process. I added this in as seen below, however, I am getting the following error after doing so and am unsure how to resolve this.
// Module Imports
const {app,BrowserWindow,dialog,ipcMain,remote} = require('electron')
var handlers = require('./routelist.js');
var dns = require('dns').promises;
var path = require('path');
var Connection = require('tedious').Connection;
var sql = require('sequelize');
var axios = require('axios');
let win
function createWindow () {
win = new BrowserWindow({webPreferences: {nodeIntegration: true}, width: 1730, height: 900, frame: false})
win.loadFile('./render/index.html')
win.webContents.openDevTools()
win.on('closed', () => {
win = null
})
}
app.on('ready', function() {
createWindow()
});
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
}
})
app.on('activate', () => {
if (win === null) {
createWindow()
}
})
Error:
Uncaught (in promise) TypeError [ERR_INVALID_ARG_TYPE]: The "id" argument must be of type string. Received type object
at validateString (internal/validators.js:112)
at Module.require (internal/modules/cjs/loader.js:768)
at require (internal/modules/cjs/helpers.js:68)
at vendor-bundle.js:7033
at new Promise (<anonymous>)
at createLoader (vendor-bundle.js:7032)
Let me know if I can provide further information.
electron now requires you to add in nodeIntegration: true in order to be able to import electron in your render process
You cannot directly use electron in a renderer process, use electron.remote:
const { remote } = require('electron');
const window = new remote.BrowserWindow({ width: 800, height: 600 });
See https://electronjs.org/docs/api/remote
After Using the chrome debugger for a really long time trying to locate when this error is happening it isn't related to electron in the slightest. The vendor bundle I am using is passing in an incorrect value for one of it's dependencies
Thanks to everyone for your time.