Im attempting to obtain an oauth token using "Implicit grant flow" in my electron app. The issue Im having is when the oauth service (in this case Twitch) redirects my electron app to the redirect uri with the token in it. When redirected the BrowserWindow seems to crash (error can be seen below). I've tried listening to multiple events provided by the BrowserWindow but all of them seem to not trigger before the crash. I've following multiple guides on how to make oauth work within Electron but none of them seem to actually work. If anybody has any success in doing this, Id very much appreciate a solution. Thanks.
Error message after being redirected
UnhandledPromiseRejectionWarning: Error: ERR_CONNECTION_REFUSED (-102) loading (redirect uri with token in it)
Code
const redirect = 'https://localhost/';
const authApp = new AuthApp(cid, redirect);
function handleAuthApp(url: string) {
const urlD = new URL(url);
console.log(urlD);
authApp.reset();
}
//Event that will trigger the AuthWindow to appear
ipcMain.on('get-auth', async (event, arg: any) => {
const window = await authApp.getWindow();
window.show();
window.on('close', () => {
authApp.reset();
console.log('closed');
});
// These events seem to never trigger
window.webContents.on('will-navigate', function(event, newUrl) {
console.log(`Navigate: ${newUrl}`);
handleAuthApp(newUrl);
});
window.webContents.on('will-redirect', function(event, newUrl) {
console.log(`Redirect: ${newUrl}`);
handleAuthApp(newUrl);
});
const filter = {
urls: [redirect+'*']
};
const { session } = window.webContents;
session.webRequest.onBeforeRedirect(filter, details => {
const url = details.url;
console.log(url);
event.returnValue = url;
window.close();
});
});
I was awaiting the URL to load before the 'will-navigate' events could be set. So the BrowserWindow would crash before the events could be fired.
Im dumb.
Related
I am building a web extension (Chrome) that checks if the external's API changed. This should be happening periodically (e.g. every 10 mins) and in the background. The plan is to have a service worker that would fire these requests, and replace extension's icon when a change in the response was detected. I have a working code that does exactly that, but I am unable to persist the service worker, and make it run on browser load (i.e. moment when the window opens). I managed to use message API, but that requires the user to click the extension button to open it and only then the extension would continuously run in the background.
This is my service worker code:
const browser = chrome || browser;
self.addEventListener("message", async (event) => {
if (event.data && event.data.type === 'CHECK_FOR_NEW_RESOURCES') {
communicationPort = event.ports[0];
const compareStructures = setInterval(async () => {
const currentStructure = await getStructure(event.data.baseURL + 'webservice/rest/server.php', event.data.token);
const {curr, newResources } = findDifferences(event.data.structure.base, currentStructure);
if(newResources > 0) {
communicationPort.postMessage({different: true, structure: curr, newResources,
time: new Date().toISOString()});
browser.action.setIcon({ path: { '48': event.data.newResourcesIcon } });
clearInterval(compareStructures);
} else {
communicationPort.postMessage({ different: false, time: new Date().toISOString() });
browser.action.setIcon({ path: { '48': event.data.noNewResourcesIcon } });
}
}, 900000);
}
});
const getStructure = async (url, token) => {
// Uses fetch() to get the resources
...
};
const findDifferences = (newStructure, oldStructure) => {
...
};
If it is not possible, what are the viable options to achieve my desired design? Could reverting to manifest ver. 2 help?
Hopefully my description makes sense, and I think it is possible, as I have seen extensions send notifications when the browser is opened.
I have a chat platform I'm building for fun. I have built notifications into the platform. On a browser it looks like this when you click the notification, with noticeSource being the user the new message is from. So it knows, to focus the page and then use the loadChat() function to load the chat associated with that user.
note.onclick=function(){
parent.focus();
console.log(this);
loadChat(noticeSource,el);
this.close()
};
However, I am also running this as PWA for android, so when it detects its on a phone it uses the Service worker showNotification method. I use this function to detect the click and to bring the app into focus.
self.addEventListener('notificationclick', function (event)
{
console.log("EVENT!",event.notification.data);
var target=event.notification.data;
const rootUrl = new URL('./index.php', location).href;
event.notification.close();
event.waitUntil(
clients.matchAll().then(matchedClients => {
for (let client of matchedClients){
console.log(client.url,rootUrl);
if (client.url.indexOf(rootUrl) >= 0){
console.log("Focus1");
return client.focus();
}
}
return clients.openWindow(rootUrl).then(
function (client) {
console.log("Focus2");
client.focus();
}
);
})
);
});
What I can't figure out is how to communicate between the SW and the client that the client should run the loadChat() function and pass along the user it should run it for. In general if someone could point me toward a resource that explains how to communicate between the SW and the client that would be appreciated. I've looked but haven't found anything and I am assuming it's because I'm not really clear on how service workers are suppose to work.
As is often the case, I found an answer after I posted the question. The client object has a method postMessage(), so once I have my client found I can use that to post a message with the userName, on the client side I can use eventListener navigator.serviceWorker.onmessage to catch messages from the sw and execute functions.
self.addEventListener('notificationclick', function (event)
{
console.log("EVENT!",event.notification.data);
var target=event.notification.data;
const rootUrl = new URL('./index.php', location).href;
event.notification.close();
console.log(clients);
event.waitUntil(
clients.matchAll().then(matchedClients => {
for (let client of matchedClients){
console.log(client.url,rootUrl);
if (client.url.indexOf(rootUrl) >= 0){
console.log(client);
client.focus();
client.postMessage(event.notification.data);
return ;
}
}
return clients.openWindow(rootUrl).then(
function (client) {
console.log("Focus2");
client.focus();
}
);
})
);
});
and on the client side
sw=navigator.serviceWorker;
sw.register('sw.js').then(function(registration){console.log("Scope:",registration.scope)});
sw.onmessage=function(event){
loadChat(event.data,document.getElementById(event.data));
}
So again like other thousand times I updated my service workers file version, to check any errors, I opened the Chrome(Browser) developer tool, and what I see... fetching works in a new way...ERROR?
Fetch finished loading: GET "https://www.example.com/favicon.ico". etc...some more CSS and image, I don't know what this is: Fetch failed loading: GET "https://www.example.com/". (the last line of console log)
Why it needs to request the domain top root every time...
Now I check the headers (DEV tools - Network - Headers) because the network status = (failed)
Request URL: https://www.example.com/
Referrer Policy: unsafe-url
pretty much no headers info at all or any content???
If I use the preload it shows extra ERROR in red (The service worker navigation preload request failed with network error:net::ERR_INTERNET_DISCONNECTED), so I have disabled preload for now, the service worker would still work with this error.
I had just updated to PHP 8.0 so maybe that was doing something, but after getting back to the old version nothing changed. Maybe my server started blocking some sort of requests, but that is unlikely, more like bad request from chrome service workers.
If the Chrome tries to check with the last request some sort of offline capacity, I use to display an offline page if fetch error, If that has anything to do with this.
Anyways despite the problems/errors described above the service worker works like it should.
Anyways, here is the SW code example:
const OFFLINE_VERSION = 1;
var filevers='xxxx';
const CACHE_NAME = 'offline'+filevers;
// Customize this with a different URL if needed.
const OFFLINE_URL = 'offlineurl.php';
const OFFLINE_URL_ALL = [
'stylesheet'+filevers+'.css',
'offlineurl.php',
'favicon.ico',
'img/logo.png'
].map(url => new Request(url, {credentials: 'include'}));
self.addEventListener('install', (event) => {
event.waitUntil((async () => {
const cache = await caches.open(CACHE_NAME);
// Setting {cache: 'reload'} in the new request will ensure that the response
// isn't fulfilled from the HTTP cache; i.e., it will be from the network.
await cache.addAll(OFFLINE_URL_ALL);
})());
});
self.addEventListener('activate', (event) => {
event.waitUntil((async () => {
// Enable navigation preload if it's supported.
// See https://developers.google.com/web/updates/2017/02/navigation-preload
//removed for now
})());
// Tell the active service worker to take control of the page immediately.
self.clients.claim();
});
self.addEventListener('fetch', (event) => {
// We only want to call event.respondWith() if this is a navigation request
// for an HTML page.
const destination = event.request.destination;
if (destination == "style" || destination == "script" || destination == "document" || destination == "image" || destination == "font") {
event.respondWith((async () => {
try {
const cache = await caches.open(CACHE_NAME);
const cachedResponse = await cache.match(event.request);
if (cachedResponse) {
return cachedResponse;
} else {
// First, try to use the navigation preload response if it's supported.
//removed for now
const networkResponse = await fetch(event.request);
return networkResponse;
}
} catch (error) {
if (event.request.mode === 'navigate') {
// catch is only triggered if an exception is thrown, which is likely
// due to a network error.
// If fetch() returns a valid HTTP response with a response code in
// the 4xx or 5xx range, the catch() will NOT be called.
const cache = await caches.open(CACHE_NAME);
const cachedResponse = await cache.match(OFFLINE_URL);
return cachedResponse;
}
}
})());
}
});
Any suggestions, what may cause the error?
We are implementing mfa(multi factor auth) for our app and I'm trying to write automation test to test this.
I don't see any direct way to get the google authenticator code for the user to login.
Has anyone tried to do this?
There is a package for that...
https://www.npmjs.com/package/totp-generator is a package that can help you with the google auth tokens. Keep in mind you want to request these right when you need them. Javascript executes asynchronously so you'll need to wrap it in a promise. Here is some example code that assumes a UI login followed by the token input.
describe('check the tokens', function()
{
// First test
it('cy.window() - get the global window object', () => { cy.viewport(500, 780)
cy.visit('https://site.domain',)
cy.get('input[name=email]').type('email#server.io')
cy.get('input[name=password]').focus().type('qwerty123')
cy.get('.Button').click()
// Now lets wait on an object that appears on the page
// when ready to input the token
cy.get(<someElement>).then(()=>{
let token = getToken();
console.log('first token: ' + token);
})
})
//Second test
it('cy.window() - get the global window object', () => { cy.viewport(500, 780)
cy.visit('https://site.domain',)
cy.get('input[name=email]').type('email#server.io')
cy.get('input[name=password]').focus().type('qwerty123')
cy.get('.Button').click()
// Now lets wait on an object that appears on the page
// when ready to input the token
cy.get(<someOtherElement>).then(()=>{
let token = getToken();
console.log('second token: '+ token);
});
})
})
function getToken () {
const totp = require('totp-generator');
const token = totp('2CQQGPPYFE7JPJAX');
return token;
}
What is the best way to manipulate DOM within an electron app?
I made some tutorials from docs using ipc and webcontents with no luck
My app is so simple, I just want to use the web like a console and showing messages (render proc) comming from the results of several sync functions (main proc)
I updated the question with real code.
I'm going to put another code, more simple to see and more simple to test (I think), is real code and works (but not like I want)
When I launch electron only writes the last message.
Ok, the response is really fast and I may not see the first messsage but to discard that I put a setTimeout and a large for() loop too, to make the uppercase function takes longer
index.js
const electron = require('electron');
const {app} = electron;
const {BrowserWindow} = electron;
const ipc = require('electron').ipcMain
app.on('ready', () => {
let win = new BrowserWindow({width: 800, height: 500});
win.loadURL('file://' + __dirname + '/index.html');
// Emitted when the window is closed.
win.on('closed', () => {
win = null;
});
// Event that return the arg in uppercase
ipc.on('uppercase', function (event, arg) {
event.returnValue = arg.toUpperCase()
})
});
index.html
<html>
<body>
<div id="konsole">...</div>
<script>
const ipc = require('electron').ipcRenderer
const konsole = document.getElementById('konsole')
// Functions
let reply, message
// First MSG
reply = ipc.sendSync('uppercase', 'Hi, I'm the first msg')
message = `Uppercase message reply: ${reply}`
document.getElementById('konsole').innerHTML = message
// Second MSG
reply = ipc.sendSync('uppercase', 'Hi, I'm the second msg')
message = `Uppercase message reply: ${reply}`
document.getElementById('konsole').innerHTML = message
</script>
</body>
</html>
You can comminicate between your frond-end and back-end with webContents and IPC. Then, you'll be able to manipulate your codes in front-end with backend's directive.
For Instance (Backend to Frontend);
Your main.js is sending a message to your front-end.
mainwindow.webContents.send('foo', 'do something for me');
Your frond-end will welcome that message here;
const {ipcRenderer} = require('electron');
ipcRenderer.on('foo', (event, data) => {
alert(data); // prints 'do something for me'
});
For Instance (Frontend to Backend);
Your frond-end;
const {ipcRenderer} = require('electron');
ipcRenderer.send('bar',"I did something for you");
Your back-end;
const {ipcMain} = require('electron');
ipcMain.on('bar', (event, arg) => {
console.log(arg) // prints "I did something for you"
event.sender.send('foo', 'done') // You can also send a message like that
})
UPDATE AFTER UPDATING QUESTION;
I tried your codes on my local, It seems like working.
Could you please try it with insertAdjacentHTML instead of 'innerHTML' to just make sure or just use console.log.
Like this;
document.getElementById('konsole').insertAdjacentHTML('beforeend',message);
"result" is a reference type value. "result" always chenge value when result = funcC() or another; Try this:
$('#msg').text(result.ToString());