ServiceWorkerRegistration.active not being set first time (Chrome) - service-worker

On Chrome Mac. I am trying to register a ServiceWorker and set a variable to it. When I call register() and the service worker has not previously been installed, the "active" property seems to be set to null immediately and then get initialized (asynchronously?) very soon after.
var sw = null;
navigator.serviceWorker.register('preview/sw.js', {scope: 'preview/'}).
then(function(registration) {
console.dir(registration);
sw = registration.active;
if (!sw) {
console.log('wat');
console.dir(registration);
}
});
In other words, I get into the if-block the first time the service worker has been installed. The console shows the active property as being set equal to the ServiceWorker in both console.dir() commands, yet the sw variable is null.
Refreshing the page fixes the problem. Anybody know what could be causing this?

For the first visit you're describing, the registration is not yet active when that promise resolves but it is "installing", so the registration's installing property will return a service worker.
Since no service worker is in the waiting state, it will then transition to activating then active. So you're right in that registration property is not initially active but on refresh, it will be.
The following code will illustrate:
navigator.serviceWorker.register('/serviceworker.js').then(onRegistration);
function onRegistration(registration) {
if (registration.waiting) {
console.log('waiting', registration.waiting);
registration.waiting.addEventListener('statechange', onStateChange('waiting'));
}
if (registration.installing) {
console.log('installing', registration.installing);
registration.installing.addEventListener('statechange', onStateChange('installing'));
}
if (registration.active) {
console.log('active', registration.active);
registration.active.addEventListener('statechange', onStateChange('active'));
}
}
function onStateChange(from) {
return function(e) {
console.log('statechange initial state ', from, 'changed to', e.target.state);
}
}
On first visit, the console.log output would be:
installing ServiceWorker {scriptURL: "http://...", state: "installing", onstatechange: null, onerror: null}
statechange initial state installing changed to installed
statechange initial state installing changed to activating
statechange initial state installing changed to activated
The state changes happen asynchronously as you observed.

The service worker is registered, but it isn't active yet and it isn't controlling your page yet.
If you want your service worker to become active, you can call the function skipWaiting in the install event handler.
If you want your service worker to control the page as soon as it becomes active, you can use the Clients.claim function in the activate event handler.
You can see an example in the ServiceWorker Cookbook Immediate Claim recipe.

Related

Electron: accessing the browser window of the single instance when called a second time

I have been able to successfully create an Electron app that links to the web app (using window.loadUrl).
I have been able to pass some command line params to the web app using window.webContents.send .... On the web app, the javascript receives the parameter and updates the screen.
I am using (2) by opening a file (right-click on it from the directory) through file association using process.argv[1]. This too works.
What I would like is that if I right-click on a second file, it must be passed on to the same electron instance. I am having some issues for this.
I have used the recommended approach for preventing multiple instances as below:
...
let myWindow = null
const gotTheLock = app.requestSingleInstanceLock()
if (!gotTheLock) {
// I do not want to quit
app.quit()
} else {
app.on('second-instance', (event, commandLine, workingDirectory) => {
...
}
In the above logic, when the program is unable to get-the-lock, the boiler-plate code quits. That works fine, in the sense, that a second window does not open. But in my case, I would like to use the process.argv[1] of the second request and pass it to the web program of the existing instance.
I have not been successful in getting a handle to the browserWindow of the other instance. I would not want to work on multiple windows where each window would call another load of the web app. The current webapp has the ability to update multiple tabs in the same window based on different parameters. Basically, that is handled in the web app itself.
What could be the solution? Thanks
I got it working. It starred at my face and I did not see it. Added a few logs and it helped. Something like this would be a solution.
...
let myWindow = null;
...
function createWindow() {
....
return win;
}
function processParams(...) {
...
}
const gotTheLock = app.requestSingleInstanceLock()
if (!gotTheLock) {
app.quit()
} else {
app.on('second-instance', (event, commandLine, workingDirectory) => {
//.. this is called the second time
// process any commandLine params
processParams(...)
...
});
app.on('whenReady')
.then(_ => {
myWindow = createWindow();
// this is called the first time
// process any argv params
processParms(...);
});
}

skipWaiting not working in Service Worker

As part of writing ServiceWorker code in typescript, I am attaching install handler and then calling skipWaiting inside it:
self.addEventListener('install', this.onInstall);
protected onInstall() {
console.log('onInstall called');
workbox.skipWaiting();
}
With this, new ServiceWorker is still in waiting state and skipWaiting doesn't seem to be working. onInstall handler gets called perfectly fine.
Is typescript implementation artifact causing problem here? Like I should write something like this?
self.addEventListener('install', event => {
self.skipWaiting();
});
Or self.skipWaiting() doesn't work the same way as workbox.skipWaiting()?
Interestingly, moving workbox.skipWaiting() to ctor where install handler is being attached fixes the issue.

Why Stop method is invoked many times in Topshelf

Topshelf is working as the windows service broker in our application. This morning, we find that the Stop mehtod is invoked many times. Here is the related code.
class Program
{
static void Main(string[] args)
{
ILog Log = new FileLog();
try
{
HostFactory.Run(serviceConfig =>
{
serviceConfig.Service<ServiceManager>(serviceInstance =>
{
serviceInstance.ConstructUsing(() => new ServiceManager());
serviceInstance.WhenStarted(execute => execute.Start());
serviceInstance.WhenStopped(execute => execute.Stop());
});
});
}
catch (Exception ex)
{
Console.WriteLine(ex);
Log.Error("Program.Main", ex, LogType.Error);
Console.ReadLine();
};
}
}
In the ServiceManager, we have the Stop mehtod, which will be invoked then TopShelf receives the stop signal from operation system.
class ServiceManager
{
xxx.....
public bool Stop()
{
try
{
_log.Info("The service is stopping", LogType.Info);
_service.StopExecuteProduceMessage();
Task.WaitAll(_tasks.ToArray());
_log.Info("The service is stopped", LogType.Info);
}
catch (Exception ex)
{
_log.Error("Stop", ex, LogType.Error);
}
return true;
}
}
This morning, we find the service is stopped with unclear reason. And there are many lines logging about this stop action.
I guess Topshelf invoke ServiceManager.Stop method many times. Anyone have encountered this problem before? I want to know I can trace why this happens.
Anyone can help? Many thanks.
You are experiencing this behavior because your Stop() method takes a while but is not being responsive to the request to stop.
Your method essentially looks like this:
Stop() {
log-stopping;
wait-a-while;
log-stopped;
}
While you are waiting, the status of the service remains "Running". This causes the requester (could be Windows itself or another program) to keep re-sending the stop request, resulting in multiple parallel/overlapping calls to Stop(). That accounts for the first 10 lines in the log you included.
You can see that it takes almost 20 seconds for the "wait" to complete (from 05:39:45 to 05:40:04).
After that, it looks like Topshelf may be stuck. That causes more messages to be sent. (Notice that in the next lines in your log, the stopping and starting pairs are logged simultaneously because your tasks are stopped and there is no waiting).
To fix the problem, you should:
Modify your WhenStopped() call to pass the HostControl parameter to Stop():
serviceInstance.WhenStopped((execute, hostControl) => execute.Stop(hostControl));
Update the Stop() method to take the HostControl parameter and make this call before the call to Task.WaitAll():
hostControl.RequestAdditionalTime(TimeSpan.FromSeconds(30));
This will inform Windows that your service has received the request and may be working on it for up to 30 seconds. That should avoid the repeated calls.
Reference: Topshelf documentation

Transaction Block has wrong Return [duplicate]

I have a problem with transactions. The data in the transaction is always null and the update handler is called only singe once. The documentation says :
To accomplish this, you pass transaction() an update function which is
used to transform the current value into a new value. If another
client writes to the location before your new value is successfully
written, your update function will be called again with the new
current value, and the write will be retried. This will happen
repeatedly until your write succeeds without conflict or you abort the
transaction by not returning a value from your update function
Now I know that there is no other client accessing the location right now. Secondly if I read the documentation correctly the updateCounters function should be called multiple times should it fail to retrieve and update data.
The other thing - if I take out the condition if (counters === null) the execution will fail as counters is null but on a subsequent attempt the transaction finishes fine - retrieves data and does the update.
simple once - set on this location work just fine but it is not safe.
Please what do I miss?
here is the code
self.myRef.child('counters')
.transaction(function updateCounters(counters){
if (counters === null) {
return;
}
else {
console.log('in transaction counters:', counters);
counters.comments = counters.comments + 1;
return counters;
}
}, function(error, committed, ss){
if (error) {
console.log('transaction aborted');
// TODO error handling
} else if (!committed){
console.log('counters are null - why?');
} else {
console.log('counter increased',ss.val());
}
}, true);
here is the data in the location
counters:{
comments: 1,
alerts: 3,
...
}
By returning undefined in your if( ... === null ) block, you are aborting the transaction. Thus it never sends an attempt to the server, never realizes the locally cached value is not the same as remote, and never retries with the updated value (the actual value from the server).
This is confirmed by the fact that committed is false and the error is null in your success function, which occurs if the transaction is aborted.
Transactions work as follows:
pass the locally cached value into the processing function, if you have never fetched this data from the server, then the locally cached value is null (the most likely remote value for that path)
get the return value from the processing function, if that value is undefined abort the transaction, otherwise, create a hash of the current value (null) and pass that and the new value (returned by processing function) to the server
if the local hash matches the server's current hash, the change is applied and the server returns a success result
if the server transaction is not applied, server returns the new value, client then calls the processing function again with the updated value from the server until successful
when ultimately successful, and unrecoverable error occurs, or the transaction is aborted (by returning undefined from the processing function) then the success method is called with the results.
So to make this work, obviously you can't abort the transaction on the first returned value.
One workaround to accomplish the same result--although it is coupled and not as performant or appropriate as just using the transactions as designed--would be to wrap the transaction in a once('value', ...) callback, which would ensure it's cached locally before running the transaction.

domain property value changes after a call to withNewSession

Below is a part of code where the discrepancy is seen. The problem that i am scratching my head over is this. I am confused over why the state of a registration changes from Active to Inactive after leaving the withNewSession block.
Transaction.withNewSession{ session ->
if (saleId?.isLong()){
invoices = registrationService.completeSale(saleId.toLong(), transactionResponse)
}
println Registration.last().status //ACTIVE
}
println Registration.last().status //INACTIVE
The following part is optional but just for reference i have pasted it. One thing the completeSale method does is it calls the following method which will activate registrations.
void activateRegistrations(SaleInvoice invoice){
Assert.notNull(invoice, 'SaleInvoice cannot be null when activating registrations.')
List<Registration> registrations = this.findRegistrationsBySaleInvoice(invoice)
registrations.each{
it.status = EntityStatus.ACTIVE
it.save()
}
}
Now, my doubt is why the first println statement i.e println Registration.last().status //ACTIVE will print ACTIVE and the second will print INACTIVE. I am guessing the Transaction.withNewSession has to do with it since the change happens in the boundary of
the withNewSession block. How can withNewSession be responsible for this apparent discrepancy?

Resources