How to stop Electron windows sharing cookies? - node.js

I am creating multiple browser windows in an electron app however I do not want them to share cookies. This is how I am currently creating the windows,
function createWindow() {
// Create the browser window.
let mainWindow = new BrowserWindow({ minWidth: 660, minHeight: 400, width: 1208, height: 680 })
// setInterval(() => sendStatus(0, 'FAIL'), 5000);
// Emitted when the window is closed.
return mainWindow;
}
However when multiple windows are open they share cookies between them.

You can define a session (or partition) for BrowserWindow instances
Browser windows in different sessions don't share cookies, so you just have to define different session for each of your windows. Like
let counter = 0
function createWindow() {
let mainWindow = new BrowserWindow({
webPreferences: {
session: session.fromPartition(`${counter++}`)
}
})
return mainWindow;
}
or any more sophisticated way.

Related

Debugging and performance profiling ManifestV3 extension Service worker

I'm learning how to build chrome extensions with manifest v3, what I'm trying to do is the following
In my extension background.js (service worker) I want to do this:
connect to WebSocket to get data updates
reconnect to the Websocket when service-worker wake up
those are tasks to get data updates from a WebSocket and update the badge text and send notifications.
I need to do these tasks while not relying on having a port open with the popup or a content script.
I'm using Chrome Alarms to wake up the service worker
it may sound weird that I have to reconnect every time the service worker wakes up considering chrome is shutting the service worker down like every 15s or less once I close the extensions dev tools (which makes me cry blood) but it is better than sending XHR periodically using Chrome alarms, which would result in a lot more requests being sent to an API, so reconnecting to the Websocket seems less problematic.
I'm having a super hard time debugging my service worker (background script) in my chrome extension. The problem is when I have dev tools open the service worker will NEVER go inactive, and what I'm trying to do is identify when the SW wakes up to perform tasks, super-duper weird because I need dev tools open to debugging...
how do you debug an extension SW without devtools open?
do you/anyone reading this have any recommendations/thoughts on what I want to do with this extension and the pain process for debugging the SW?
here is the code I have for the background.js
const extension = {
count: 0,
disconnected: false,
port: {} as any,
ws: null,
};
const fetchData = () => {
return fetch(
'https://api.coingecko.com/api/v3/coins/ethereum?localization=incididuntelit&tickers=false&market_data=true&community_data=true&developer_data=true&sparkline=true'
).then((res) => res.json());
};
// Chrome Alarms
const setupAlarms = () => {
console.log('###ALARMS-SETUP');
chrome.alarms.get('data-fetch', (alarm) => {
if (!alarm) {
chrome.alarms.create('data-fetch', { periodInMinutes: 0.1 });
}
});
chrome.alarms.get('client-pinging-server', (alarm) => {
if (!alarm) {
chrome.alarms.create('client-pinging-server', { periodInMinutes: 0.1 });
}
});
chrome.alarms.onAlarm.addListener((e) => {
if (e.name === 'data-fetch') {
fetchData()
.then((res) => {
// console.log('###ETHEREUM:', res.market_data.current_price.cad);
chrome.action.setBadgeText({ text: `${Math.round(Math.random() * 100)}` });
})
.catch((error) => console.error('###ERROR', error));
}
if (e.name === 'client-pinging-server') {
if (!extension.ws || !extension.ws.getInstance()) {
console.log('\n');
console.log('###reconnecting...', extension.ws);
console.log('\n');
extension.ws = WebSocketClient();
extension.ws.connect();
}
if (extension.ws.getInstance()) {
console.log('###sending-client-ping');
extension.ws.getInstance().send(JSON.stringify({ message: 'client ping - keep alive' }));
}
}
});
};
// ON INSTALL
chrome.runtime.onInstalled.addListener(async (e) => {
const API_URL = 'ws://localhost:8080';
chrome.storage.local.set({ apiUrl: API_URL, count: 0 });
console.info('###Extension installed', e);
chrome.action.setBadgeText({ text: '0' });
chrome.action.setBadgeBackgroundColor({ color: '#FF9900' });
});
// ON SUSPEND
chrome.runtime.onSuspend.addListener(() => {
console.log('Unloading.');
chrome.action.setBadgeText({ text: `off` });
});
function WebSocketClient() {
let instance = null;
const connect = () => {
return new Promise((resolve, reject) => {
const ws = new WebSocket('ws://localhost:8080');
const onOpen = () => {
instance = ws;
console.log('###websocket:connected', instance);
return resolve(ws);
};
const onError = (event) => {
console.log('###INIT-FAILED', event);
ws.close(1000, 'closing due to unknown error');
return reject('failed to connect to websocket');
};
const onClose = () => {
console.log('###websocket:disconnected');
instance = null;
// reconnect is happening in the alarm callback
};
ws.onopen = onOpen;
ws.onerror = onError;
ws.onclose = onClose;
});
};
const getInstance = () => {
return instance;
};
return {
connect,
getInstance,
};
}
self.addEventListener('install', async (event) => {
console.log('====install', event);
chrome.action.setBadgeBackgroundColor({ color: '#a6e22e' });
});
self.addEventListener('activate', async (event) => {
console.log('====activate', event);
chrome.action.setBadgeBackgroundColor({ color: '#FF9900' });
extension.ws = WebSocketClient();
extension.ws.connect();
setupAlarms();
});
self.addEventListener('push', function (event) {
// Keep the service worker alive until the notification is created.
event.waitUntil(
self.registration.showNotification('Testing PUSH API', {
body: 'coming from push event',
})
);
});
Since Devtools can attach to multiple contexts at once, you can open it for another context so the SW will be secondary and thus will be able to unload normally.
Debugging
Open any visible page of the extension or, if there are none, its manifest.json URL:
chrome-extension://ID/manifest.json where ID is the extension's id
Open devtools and switch to its Application tab, then choose Service worker on the left.
Click start (if shown) to start the service worker, click the background script name to open it in the Sources panel, set breakpoints, etc.
Click stop to stop the service worker, optionally click Update at the top, and skip waiting in the middle (if shown) to force an update.
Click start again - your breakpoints will trigger.
Performance profiling
Open any visible page of the extension or, if there are none, its manifest.json URL:
chrome-extension://ID/manifest.json where ID is the extension's id
Open devtools and switch to its Application tab, then choose Service worker on the left.
Duplicate the tab, open devtools there, go to Performance tab, click "Start" or press Ctrl-E
Switch back to the first tab and click the start button (or stop first, then start). In certain cases you may also see skip waiting in the middle, click it then.
Switch to the second tab, wait for a while and click the recording button again or press Ctrl-E.
Notes
When the service worker is started you can see its context in the Sources panel on the left (in the files panel), on the top-right (in the threads panel), in the console toolbar (the context selector).
This may seem unwieldy at first, but once you try and get the knack of it, it's quite trivial and can even beat devtools that's shown when clicking the "service worker" link in chrome://extensions page because this one a) shows extension's localStorage/IndexedDB in devtools, b) provides control over service worker lifetime/execution, c) supports performance profiling of SW startup.
Note that the ManifestV3 documentation's claims about benefits provided by service workers for extensions are largely exaggerated or completely false, e.g. in your case it's clear that restarting the service worker is bad, so you should use a port to prolong the SW's lifetime as much as possible.

Electron: How to create a new BrowserWindow according to the closed event of the last window?

There are two BrowserWindow instances, and browser2 will create according to browser1 closing. The question is that the closed event of browser1 propagates and impacts browser2. How can I prevent the closed event to impact browser2 even though browser2 is listening the closed event?
Demo is following:
let browser1 = new BrowserWindow({
width: 800,
height: 600,
})
browser1.on('closed', (e) => {
let browser2 = BrowserWindow({
width: 800,
height: 600,
})
browser2.on('closed', (e) => {
console.log("browser2 received closed event")
}
})
Use the close event rather than the closed event.
close: Triggered while the window is still open, and is about to be closed.
closed: Triggered after the window is closed.
Like this:
browser1.on('close', (e) => {
Instead of:
browser1.on('closed', (e) => {
PS: your second `BrowserWindow` is missing the `new` keyword like this: ` = new BrowserWindow`, and the 2nd `closed` event is missing a closing `)`.

Why is the variable undefined? - Node - ElectronJs

I am creating an Electron app and I am trying to split my code in different scripts to make it more manageable; however, for some reason one of the variables in my script keeps returning undefined and I can't figure out why. I already checked similar questions here on SO, but did not find an answer.
I have a file called windowManipulation.js and this is part of it:
let signInWindow;
module.exports.createSignInWindow = () => {
signInWindow = new BrowserWindow({
show: false,
width: 1500,
height: 800,
webPreferences: {
nodeIntegration: true
}
});
signInWindow.loadFile(`views/logIn.html`)
signInWindow.once("ready-to-show", () => {
signInWindow.show();
});
signInWindow.on("close", () => {
signInWindow = null;
});
signInWindow.on('crashed', () => {
app.relaunch();
app.exit(0);
})
}
module.exports.closeSignInWindow = () => {
signInWindow.close();
signInWindow = null;
}
Now, when I call the function to create the window it creates it without a problem. But when I call the function to close it, it says that signInWindow is undefined.
Why is it undefined if it was supposed to be set when the signInWindow was created? What am I doing wrong?
It sounds like createSignInWindow and closeSignInWindow are being called from different processes. Being different processes, they each their own memory, and each would execute this file independently. So if you create the window in the main process, and close it from the window process, the window process will not think the variable exists.
So it sounds like you need to use ipcRenderer to communicate from the render to the main process so that it can close the window for you.
It'd be something like:
// renderer
const { ipcRenderer } = require('electron')
ipcRenderer.send('close-signin')
// main
const { ipcMain } = require('electron')
ipcMain.on('close-signin', closeSignInWindow)

New Window Positioning in Electron

I need to know how to open new windows which is offset a little by the current window position (first window will be opened in the center)
My codes are as follows:
// index.js
const {app,BrowserWindow,Menu,MenuItem,dialog}=require('electron');
function window_open(path){
win = new BrowserWindow({show: false})
win.loadURL(path);
win.once('ready-to-show', () => {win.show()})
}
let win
app.on('ready',event=>{
'use strict';
window_open(`file://${__dirname}/index.html`)
});
This opens the initial window in the center. I am also passing this function in the new window command (cmd+n)
{
label: 'File',
submenu: [
{label: 'New Window', accelerator: 'CmdOrCtrl+N', click: () => (
window_open(`file://${__dirname}/index.html`))
},
The code works fine, except that every window is positioned the same, in the center. I would like each new windows to be offset a little.
What's the best way to achieve this?
I learned that I need these two things:
BrowserWindow.getFocusedWindow()
win.getPosition()
Combining with #pergy's response, I got the following code which finds the focused window if there is any and offsets a new window from its position, otherwise creates a new window in the center:
let win = null;
function window_open(path) {
const opts = { show: false };
if (BrowserWindow.getFocusedWindow()) {
current_win = BrowserWindow.getFocusedWindow();
const pos = current_win.getPosition();
Object.assign(opts, {
x: pos[0] + 22,
y: pos[1] + 22,
});
};
win = new BrowserWindow(opts);
win.loadURL(path);
win.once('ready-to-show', () => { win.show() });
};
app.once('ready', event => {
window_open(`file://${__dirname}/index.html`);
});
This does what I asked for in my original question, so I have decided to post this. However, I do feel that it is slow in spawning the new windows, so I won't mark this as an answer to see if there are faster approaches to this.
Update:
I have found out that waiting on 'ready-to-show' is what makes it slow, as it waits for the ready state. I have accepted this as the answer as I feel that the speed issue is dependent to the content and not the browser. Feel free to add comments on this as I am still open ears.
You can define the window's position in constructor option x and y. The currently active window's position can be retrieved with getPosition(), so you can define offset for the new window from that.
See this dummy app for example:
const { app, BrowserWindow } = require('electron')
let win = null
app.once('ready', () => {
const openWindow = () => {
const opts = {
show: false
}
if (win) {
const pos = win.getPosition()
Object.assign(opts, {
x: pos[0] + 10,
y: pos[1] + 10
})
}
win = new BrowserWindow(opts)
win.loadURL('http://google.com')
let thisWin = win
win.once('ready-to-show', () => {
thisWin.show()
})
}
setInterval(openWindow, 5000)
})

Electron pass BrowserWindow to external main function (not IPC)

I understand and can implement both IPC and .remote() from main <--> renderers.
This question is about an external function still in the main thread and sharing the instantiated BowerserWindow.
For example, main.js:
...
let mainWindow = null;
...
mainWindow = new BrowserWindow({
show: false,
width: 1024,
height: 728
});
...
I'm trying to access mainWindow from foo.js and can't seem to get there.
Psuedocode:
export default () => {
let win = mainWindow // from main.js;
win.webContents.send('toast', 'woohoo'); // Arbitrary Render side listener
}
This works, although it seems like additional overhead as a lookup:
On main.js
foo(mainWindow.id)
On foo.js(id: number)
const win = BrowserWindow.fromId(id);
win.webContents.send('toast', 'woohoo');
I welcome any more efficient method.

Resources