How to do a silent refresh via implicit flow in Angular? - javascript

so I have the problem that our token won't refresh. More than that, our entire website is getting duplicated. Here's the background:
We have the following auth config (more or less):
export const authConfig: AuthConfig = {
issuer: '[censored]',
redirectUri: window.location.origin + '/',
silentRefreshRedirectUri: window.location.origin + '/assets/login-sources/silent-refresh.html',
tokenEndpoint: '[censored]',
loginUrl: '[same as tokenEndpoint]',
clientId: '[censored]',
scope: '[censored]',
clearHashAfterLogin: true,
oidc: true,
};
And we also have a loginService, which does roughly the following onInit:
this.oauthService.configure(authConfig);
this.oauthService.tokenValidationHandler = new JwksValidationHandler();
// this.oauthService.setupAutomaticSilentRefresh(); - didn't work
this.oauthService.loadDiscoveryDocument([censored]).then((doc) => {
// Subscribe to expiration event to refresh token.
this.oauthService.events
.pipe(filter(element => element.type === 'token_expires'))
.subscribe(
(a) => {
console.log("Token is about to expire! Refreshing!");
this.oauthService.silentRefresh().then(result => console.log(result)).catch(error => console.error(error));
}
);
if (this.userid == null) {
this.oauthService.tryLoginImplicitFlow().then((loggedIn) => {
if (!loggedIn) {
this.oauthService.initLoginFlow();
}
});
}
});
The problem: whenever the token expired, the following things happen:
The entire website clones its instance so two instances are running in parallel (according to console)
=> this is even worse if we use automaticSilentRefresh without all those event listeners. There, it clones itself indefinitely until it runs out of RAM
A few seconds later we get a "refresh timeout" error in the console, without a stack trace.
So I looked into the code of silentRefresh() and it seems it works with an iframe. In said iframe SHOULD be just simple code that is used to refresh the token and "communicate to the main application" see here. We even implemented the refresh.html like here. In our case however, we have an iframe where the entire website is mirrored. This means we have something like this:
html
head /head
body
app-root /app-root
iframe
app-root /app-root
/iframe
/body
/html
Since for every silentRefresh(), the iframe gets removed and re-added into the DOM, each time a new instance of the whole website is created. What did I do wrong and how can I fix this?
Versions:
Angular 9 +
"angular-oauth2-oidc": "^9.0.1",
"angular-oauth2-oidc-jwks": "^9.0.0".

One way to deal with this, which I use for some samples of mine, is to only render the main app when running on the main window. In my small sample I use index.html for both the main app and the renewal iframe, but vary the code that runs.
Mine is a ReactJS Sample and this is the technique I have used. Hopefully this helps in a manner that isn't too intrusive, though there may be alternative solutions.
if (window.top === window.self) {
// Run the main ReactJS app
render (
<App />,
document.getElementById('root'),
);
} else {
// Run a minimal app to handle the iframe renewal
const app = new IFrameApp();
app.execute();
}

Related

Are all these multiple checks really required to handle a new service worker update?

I am following a tutorial on service workers on Udacity by Jake Archibald, and this is the solution skeleton of an exercise on "updating" that has confused me:
They give a long solution that is like the code below (they check for three possible cases, as explained in comments):
(async() => {
if ("serviceWorker" in navigator) {
try {
const reg = await navigator.serviceWorker.register("/sw.js");
if (!reg || !navigator.serviceWorker.controller) {
return;
}
// Possible states of new updates:
// - 1. There are no updates yet, a new update may arrive
// - 2. An update is in progress
// - 3. A waiting update exists (already installed)
// 1.
addEventListener("updatefound", () => {
console.log("updatefound");
const sw = reg.installing;
trackInstallation(sw);
});
// 2
const installingSw = reg.installing;
if (installingSw) {
console.log("installingSw");
trackInstallation(installingSw);
return;
}
// 3
if (reg.waiting) {
console.log("reg.waiting");
const sw = reg.waiting;
notifyUpdate();
return;
}
console.log("nothing");
} catch (error) {
console.error("Service worker registration failed.", error);
}
}
})();
function trackInstallation(worker) {
worker.addEventListener("statechange", () => {
if (worker.state === "installed") {
notifyUpdate();
}
});
}
function notifyUpdate() {
alert("There's a new update!");
}
But I tried different scenarios and I can't get these checks except the third one (if (reg.waiting) {) to be triggered. So I wonder if all these checks are really needed?
This is how I trigger the third check:
Install the service worker (register, install, activate) by loading the web page for the first time at localhost:8080
Make a change to the service worker (e.g., add "/tmp.txt" to the array of names of the files that have to be cached)
Refresh the page.
👉 First load none of the checks are triggered.
👉 Second load, reg.waiting runs (the third check is triggered).
which makes sense (I've read and I know how the lifecycle of service workers works), but I don't know in what scenario the other two checks (i.e., the ones on line 14 and 21) would be triggered?
Things to remember:
The service worker isn't for a single page
The service worker runs independently of any page
In your example code, one page installs the service worker, and ensures that the install completes entirely.
That isn't always the case in the real world. Let's say:
You refresh a service-worker-controlled page.
That triggers a service worker update check. Assuming an update is found:
// 1 will happen for all pages in the origin. You might not be seeing it, because your code is addEventListener('updatefound', where it should be navigator.serviceWorker.addEventListener('updatefound'.
If at this point, you reload the page, you might hit // 2, since there's already an install in progress.
For more details on the service worker lifecycle, see https://web.dev/service-worker-lifecycle/

Electron "ready-to-show" event not working as expected

Here is a block of code from my application Codey.
src/main.py
// Show window once it has finished initialising
docsWindow.once("ready-to-show", () => {
if (darkMode) {
docsWindow.webContents.send("dark-mode:toggle");
}
if (!isDarwin) {
docsWindow.webContents.send("platform:not-darwin");
}
docsWindow.webContents.send("docs:jump", section);
docsWindow.show();
});
src/docs/renderer.js
window.api.darkMode.toggle.receive(toggleDarkMode);
When darkMode = true, toggleDarkMode is never run.
My application has two different windows - an editor and a docs window. For both windows, on the "ready-to-show" event, "dark-mode:toggle" is sent to the renderer process. However, the docs window fails to run the toggleDarkMode function whilst it works for the editor window.
Note: The application must be packaged using "yarn package" as some features do not work in the dev environment.
Any help will be much appreciated.
(Repo: https://github.com/Liamohara/Codey)
For anyone that's interested. I found a solution to my issue.
src/main.py
// Show window once it has finished initialising
- docsWindow.once("ready-to-show", () => {
+ docsWindow.webContents.once("did-finish-load", () => {
if (darkMode) {
docsWindow.webContents.send("dark-mode:toggle");
}
if (!isDarwin) {
docsWindow.webContents.send("platform:not-darwin");
}
docsWindow.webContents.send("docs:jump", section);
docsWindow.show();
});
In the docs window, there is more HTML content to be renderered than in the editor window. As a result, the DOM takes longer to load and the "ready-to-show" event is emitted before it has finished. Using the "did-finish-load" event, ensures that the API functions are not called before the DOM has finished loading.

Quitting an electron app after setInterval() from external script file

Edited for more clarity.
I am trying to make a splash screen applet - to start learning nodejs and electron.
I want to create an applet that launches, shows some messages every 10 seconds, and then quits.
I'm basing it off the Discord app and Teams where they have pop up loading screens that have a progress bar, and once completed load the full application.
I want to know how to do it before the "load full app" portion kicks in and how to close the splash screen completely.
Currently I have an index.js, index.html, and a main.js.
index.js is the electron browser window. index.html is the main rendered page, and the main.js is the timer to switch the innerHTML based on the time from:
// main.js
var startTime = 0,
totalTime = 10,
timeBuffer = 2,
totalPercent = 0,
timeCounter = setInterval( progress, 1000 );
function progress() {
if( (startTime += 1) >= (totalTime + timeBuffer + 1) ) {
// quit app (1)
} else {
// show messages here
}
}
At point (1) in the code, I've tried adding in app.close(); but that fails since I haven't added in app. I tried adding it in but that doesn't work either.
I tried adding in:
// main.js
const { ipcRenderer } = require('electron');
ipcRenderer.send('close-me');
//index.js
ipcMain.on( 'close-me', (evt, arg) => {
app.quit();
});
But this didn't work either. I'm still trying to understand the relationship between index.js and the other scripts you might write for the app - but thought quitting the app entirely would be easy.
It seems app.quit() wasn't working in this instance, but app.close(0) was.
Reading through the docs didn't suggest any reasons to why but in case someone else runs into this problem too.
Store your setInterval()as a variable and use JavaScript clearInterval(variable) as a page exit action.

Is it possible to keep a subscription running even app is closed?

For background purposes I made a deep search in ionic documentation (native and no native), and in other pages, and found that may be there is a posibility to achieve this (keep running a subscription even the app was closed), but I can't understand at all where I should put one thing or other thing to make BakckgroundMode plugin working... (I think this plugin is not working in my device), but it's hard to inspect that thing because you can't inspect your device if it's in background mode with chrome://inspect...
This is the BackgroundMode plugin:
https://github.com/katzer/cordova-plugin-background-mode
I have installed backgroundMode 0.7.2 which isn't the most actualized plugin, but trying to install the latest gives me errors on build, so i tried this version and works.
In the docs, I see the following:
The plugin creates the object cordova.plugins.backgroundMode and is
accessible after the deviceready event has been fired.
So in order to run background mode, in my page.ts inside constructor (later I will talk about why in the constructor), I put:
document.addEventListener('deviceready', ()=> {
this.backgroundMode.enable();
this.backgroundMode.setDefaults(
{
title: 'executing in bg mode...',
text: 'background mode was activated successfully!',
color: '#fff',
resume: true,
silent: false
}
);
this.backgroundMode.disableWebViewOptimizations();
this.backgroundMode.on('activate').subscribe(()=>{
this.isAcceptedObservable = this.localNotifications.on('yes').subscribe(res =>{
console.log('confirmed!');
this.backgroundMode.unlock();
});
//And more observables...
});
});
the subscriptions inside backgroundMode.on('activate') subscription are the things that I need to keep running even if app is closed.
the reason of why I was put that code in constructor is because I'm using also ngx-socket-io to listen to emit and broadcast functions of a socket server in node that i created, so one of the subscriptions that is linked to socket events is:
this.getInvitationSubscriber = this.getInvitation().subscribe(data =>{
this.invitations.push(data);
this.showNotification({
title: 'Invitation received!',
text: 'Someone invited you!',
icon: data['from']['avatar_src'],
attachments: [data['from']['invitation_src']]
});
console.log(this.invitations);
});
in ionViewWillEnter i call:
ionViewWillEnter{
this.socket.connect();
}
and this function is outside the constructor:
getInvitation(){
let observable = new Observable(observer =>{
this.socket.on('subject', data =>{
observer.next(data);
});
});
return observable;
}
I'm expecting to keep running the 'notifications listener' that I have in constructor inside the backgroundMode.on('activate') function to receive notifications even app is closed, but I dont know if it's possible with ngx-socket-io and the structure that i have.
also I have injected background mode in app.module providers and in otherpage.module.ts provider and imported correctly so that's not the error, but I'm not getting notifications...
I know that notifications scheduling are correct because if i simple try not to use backgroundMode and putting that subscriptions outside backgroundMode.on('activate') subscription, i'm getting again notifications, but only works if app is in foreground or in suspended...

How can I access the DOM of a <webview> in Electron?

I'm just getting started with Electron, with prior experience with node-webkit (nw.js).
In nw.js, I was able to create iframes and then access the DOM of said iframe in order to grab things like the title, favicon, &c. When I picked up Electron a few days ago to port my nw.js app to it, I saw advice to use webviews instead of iframes, simply because they were better. Now, the functionality I mentioned above was relatively easy to do in nw.js, but I don't know how to do it in Electron (and examples are slim to none). Can anyone help?
Also, I have back/forward buttons for my webview (and I intend on having more than one). I saw in the documentation that I could call functions for doing so on a webview, but nothing I have tried worked either (and, I haven't found examples of them being used in the wild).
I dunno who voted to close my question, but I'm glad it didn't go through. Other people have this question elsewhere online too. I also explained what I wanted to achieve, but w/e.
I ended up using ipc-message. The documentation could use more examples/explanations for the layperson, but hey, I figured it out. My code is here and here, but I will also post examples below should my code disappear for whatever reason.
This code is in aries.js, and this file is included in the main renderer page, which is index.html.
var ipc = require("ipc");
var webview = document.getElementsByClassName("tabs-pane active")[0];
webview.addEventListener("ipc-message", function (e) {
if (e.channel === "window-data") {
// console.log(e.args[0]);
$(".tab.active .tab-favicon").attr("src", e.args[0].favicon);
$(".tab.active .tab-title").html(e.args[0].title);
$("#url-bar").val(e.args[0].url);
$("#aries-titlebar h1").html("Aries | " + e.args[0].title);
}
// TODO
// Make this better...cancel out setTimeout?
var timer;
if (e.channel === "mouseover-href") {
// console.log(e.args[0]);
$(".linker").html(e.args[0]).stop().addClass("active");
clearTimeout(timer);
timer = setTimeout(function () {
$(".linker").stop().removeClass("active");
}, 1500);
}
});
This next bit of code is in browser.js, and this file gets injected into my <webview>.
var ipc = require("ipc");
document.addEventListener("mouseover", function (e) {
var hoveredEl = e.target;
if (hoveredEl.tagName !== "A") {
return;
}
ipc.sendToHost("mouseover-href", hoveredEl.href);
});
document.addEventListener("DOMContentLoaded", function () {
var data = {
"title": document.title,
"url": window.location.href,
// need to make my own version, can't rely on Google forever
// maybe have this URL fetcher hosted on hikar.io?
"favicon": "https://www.google.com/s2/favicons?domain=" + window.location.href
};
ipc.sendToHost("window-data", data);
});
I haven't found a reliable way to inject jQuery into <webview>s, and I probably shouldn't because the page I would be injecting might already have it (in case you're wondering why my main code is jQuery, but there's also regular JavaScript).
Besides guest to host IPC calls as NetOperatorWibby, it is also very useful to go from host to guest. The only way to do this at present is to use the <webview>.executeJavaScript(code, userGesture). This api is a bit crude but it works.
If you are working with a remote guest, like "extending" a third party web page, you can also utilize webview preload attribute which executes your custom script before any other scripts are run on the page. Just note that the preload api, for security reasons, will nuke any functions that are created in the root namespace of your custom JS file when your custom script finishes, however this custodial process will not nuke any objects you declare in the root. So if you want your custom functions to persist, bundle them into a singleton object and your custom APIs will persist after the page fully loads.
[update] Here is a simple example that I just finished writing: Electron-Webview-Host-to-Guest-RPC-Sample
This relates to previous answer (I am not allowed to comment): Important info regarding ipc module for users of Electron 1.x:
The ipc module was split into two separate modules:
ipcMain for the main process
ipcRenderer for the renderer process
So, the above examples need to be corrected, instead of
// Outdated - doesn't work in 1.x
var ipc = require("ipc");
use:
// In main process.
var ipcMain = require('electron').ipcMain
And:
// In renderer process.
var ipcRenderer = require('electron').ipcRenderer
See: http://electron.atom.io/blog/2015/11/17/electron-api-changes section on 'Splitting the ipc module'

Categories

Resources