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.
Related
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/
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();
}
We have created a full fledged UWP webview app (WinJs) with help of Visual Studio 2017. Recently going through microsoft documentation and stackoverflow threads, we found out that the uwp app can be launched full screen with title bar removed.
Following code needed to inserted into App.Xaml.Cs file
ApplicationView view = ApplicationView.GetForCurrentView();
view.TryEnterFullScreenMode();
But the problem here is that, we are unable to locate this file to insert this. May be its because of WinJS template i have chosen, i don't know.
Other notables files include main.js | packageapp.manifest file. I do not know whether this code could be integrated with either of this file.
Edit:
With roy's help, the main js file is modified according to the sample given in windows universal sample github, but still full screen does not seem to open up.
The main.js file code is as given below
(function () {
"use strict";
var app = WinJS.Application;
var activation = Windows.ApplicationModel.Activation;
var isFirstActivation = true;
var ViewManagement = Windows.UI.ViewManagement;
var ApplicationViewWindowingMode = ViewManagement.ApplicationViewWindowingMode;
var ApplicationView = ViewManagement.ApplicationView;
function onLaunchInFullScreenModeChanged() {
ApplicationView.preferredLaunchWindowingMode = launchInFullScreenMode.checked ? ApplicationViewWindowingMode.fullScreen : ApplicationViewWindowingMode.auto;
}
app.onactivated = function (args) {
if (args.detail.kind === activation.ActivationKind.voiceCommand) {
// TODO: Handle relevant ActivationKinds. For example, if your app can be started by voice commands,
// this is a good place to decide whether to populate an input field or choose a different initial view.
}
else if (args.detail.kind === activation.ActivationKind.launch) {
launchInFullScreenMode.addEventListener("click", onLaunchInFullScreenModeChanged);
launchInFullScreenMode.checked = ApplicationView.preferredLaunchWindowingMode == ApplicationViewWindowingMode.fullScreen;
// A Launch activation happens when the user launches your app via the tile
// or invokes a toast notification by clicking or tapping on the body.
if (args.detail.arguments) {
// TODO: If the app supports toasts, use this value from the toast payload to determine where in the app
// to take the user in response to them invoking a toast notification.
}
else if (args.detail.previousExecutionState === activation.ApplicationExecutionState.terminated) {
// TODO: This application had been suspended and was then terminated to reclaim memory.
// To create a smooth user experience, restore application state here so that it looks like the app never stopped running.
// Note: You may want to record the time when the app was last suspended and only restore state if they've returned after a short period.
}
}
if (!args.detail.prelaunchActivated) {
// TODO: If prelaunchActivated were true, it would mean the app was prelaunched in the background as an optimization.
// In that case it would be suspended shortly thereafter.
// Any long-running operations (like expensive network or disk I/O) or changes to user state which occur at launch
// should be done here (to avoid doing them in the prelaunch case).
// Alternatively, this work can be done in a resume or visibilitychanged handler.
}
if (isFirstActivation) {
// TODO: The app was activated and had not been running. Do general startup initialization here.
document.addEventListener("visibilitychange", onVisibilityChanged);
args.setPromise(WinJS.UI.processAll();
launchInFullScreenMode.addEventListener("click", onLaunchInFullScreenModeChanged);
launchInFullScreenMode.checked = ApplicationView.preferredLaunchWindowingMode == ApplicationViewWindowingMode.fullScreen;
}
isFirstActivation = false;
};
function onVisibilityChanged(args) {
if (!document.hidden) {
// TODO: The app just became visible. This may be a good time to refresh the view.
}
}
app.oncheckpoint = function (args) {
// TODO: This application is about to be suspended. Save any state that needs to persist across suspensions here.
// You might use the WinJS.Application.sessionState object, which is automatically saved and restored across suspension.
// If you need to complete an asynchronous operation before your application is suspended, call args.setPromise().
};
app.start();
})();
Any suggestions on how to solve this problem.
You could put the code in a JS file that will be referenced by the HTML that you are showing.
There used to be a JS UWP FullScreenMode Sample here: FullScreenMode JS. Although it is archived, you could still take a look at scenario2-launch.js and scenario1-basic.js. It shows how to these APIs are used in JavaScript.
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'
I am using the Counter-Up plugin for my Meteor application.
On first site load it works fine, but there seems to be a problem with real-time changes.
I want to display total games created in my web app, so I have this helper:
totalGames: function () {
return Games.find().count();
}
and this is my rendered function:
Template.home.rendered = function () {
$('.counter').counterUp({
delay: 10,
time: 500
});
};
Now the problem is, the counter does not increase, due to the reactivity. So if user A sees the counter with the number 1 on the home site and suddenly a new game is created, the number changes to 12 and not 2.
How can I solve this issue?
Any help would be greatly appreciated.
Give this a try:
Template.home.rendered = function() {
this.autorun(function() {
if (Template.currentData() && Games.find().count())
$('.counter').counterUp({delay: 10, time: 500});
});
};
In theory, this should rerun the counterUp initialization any time the Games count changes. The Template.currentData business is just a hack to make the the autorun work inside of rendered - see my related answer here.
When I get this behavior I run this. I've also run into issues with randomness. The delay is caused by the minimongo which sits on your browser not getting the correct cache from mongodb.
Here are things that I do to kick start the ddp to get the data through the wire.
1) I close out of the terminal. This should have 0 affect, but sometimes things get hung up. Just Try it.
2) rm -rf ~/[name of project].meteor/local/.mirror
3) double check I saved my changes.