service worker not active initially, reload needed to activate - javascript

I'm experimenting with service workers, so my scenario is very basic. The problem I'm experiencing is that when I load my page the first time, the service worker doesn't do anything (except installing) and when I reload the page, the service worker is active and intercepts.
Here is my service worker code:
self.addEventListener("install", function (event) {
// self.skipWaiting();
});
self.addEventListener("fetch", function (event) {
const url = event.request.url;
if (url.includes("todos")) {
const response = {
body: { data: "barfoo" },
init: {
status: 200,
statusText: "OK",
headers: {
"Content-Type": "application/json",
"X-Mock-Response": "yes",
},
},
};
const mockResponse = new Response(JSON.stringify(response.body),
response.init);
event.respondWith(mockResponse);
}
});
If the URL contains todo it sends a mock response back.
The last piece is my index.html:
<html>
<body>
<h1>Test page with service worker</h1>
<button>Fetch</button>
<script>
const registerServiceWorker = async () => {
if ("serviceWorker" in navigator) {
try {
const registration = await navigator.serviceWorker.register(
"/service-worker.js",
// {
// scope: "/sw-test/",
// }
);
if (registration.installing) {
console.log("Service worker installing");
} else if (registration.waiting) {
console.log("Service worker installed");
} else if (registration.active) {
console.log("Service worker active");
}
} catch (error) {
console.error(`Registration failed with ${error}`);
}
}
};
document
.querySelector("button")
.addEventListener("click", async (e) => {
const output = await fetch("https://jsonplaceholder.typicode.com/todos/1");
const json = await output.json();
console.log("OUTPUT", json);
});
registerServiceWorker();
</script>
In the html (all is served from http://localhost:8080 btw) is a button which triggers the fetch call on click.
As I said before, the first time I get Service worker installing in the console. Then, when I hit the button I receive the response from jsonplaceholder (self.skipWaiting() didn't help). I can hit the button many times with the same result. Now, when I hit reload I get Service worker active in the console and everything is now working as expected (I get the mock response)
So my question is, what do I have to do to get mock responses initially so the reload is not needed?

Related

socket.io on client side not listening to server events

The socket have to listen to the server when i send a new message.
The messages arrive because when i refresh the page i can see them.
Server side socket i know is working find because in other frontend app the same client socket is working find.
This socket is called every time the user select a new chat in the web.
Here is the socket service in my app:
import openSocket from "socket.io-client";
function connectToSocket() {
return openSocket("http://localhost:8080");
}
export default connectToSocket;
And here is the code executed when user select a chat:
// how i am importing the socket
import openSocket from "../../Services/socket-io"
async function fetchMessages(ticketId) {
try {
const { data } = await api.get("/messages/" + ticketId, {
params: { pageNumber },
});
if (ticketId === data.ticket.id) {
await loadMessages(data, ticketId);
}
listenMessages(ticketId);
} catch (err) {
Toast.ToastError("Error trying to load messages");
}
};
function listenMessages(ticketId) {
const socket = openSocket();
socket.on("connect", () => socket.emit("joinChatBox", ticketId));
socket.on("appMessage", (data) => {
if (data.action === "create") {
console.log(data);
}
if (data.action === "update") {
console.log(data);
}
});
}
What i already tried:
Use the same socket version both in client and server (3.0.5).
Calling listenMessages every time a message is sent.
socket.io-client version 4.

Get API response time using performance.now() in Service Worker

I am developing a react application and intercepting every fetch request using a service worker in place. I need to get the API response time what we normally do using performance.now() using service worker as if I am adding eventListener on its fetch method, it can intercept every fetch request.
I am not sure exactly how to achieve this and where to place performance.now() start and end calls?
Below is the code I have worked so far.
registerServiceWorker function - The function is womring fine and sw.js registers itself as service worker.
const registerServiceWorker = () => {
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/sw.js').then(registration => {
console.log('Service worker registered with scope: ', registration.scope);
}, (err) => {
console.log('ServiceWorker registration failed: ', err);
});
});
} else {
console.log('Service worker is not supported!');
};
};
sw.js file - Service worker file
self.addEventListener('fetch', event => {
const apiStartTime = self.performance.now();
event.respondWith((async () => {
const { pathname, query } = new URL(event.request.url);
const apiTotalTime = Math.round(self.performance.now() - apiStartTime);
console.log('performance2 end', event.request.url, apiTotalTime);
const cachedResponse = await caches.match(event.request);
if (cachedResponse) return cachedResponse;
const response = await fetch(event.request);
return response;
})());
});
The apiTotalTime value is coming as 0 always for every API which is not as expected.
Try placing your second call to performance.now after await fetch(event.request) - after the service call is actually made and awaited.

registration.showNotification is not a function

I'm using serviceworker-webpack-plugin to create a service worker in my reactjs apps.
I've followed the example to register the service worker in the main thread. I've learnt that Html5 Notification doesn't work on Android chrome, so I used registration.showNotification('Title', { body: 'Body.'}); instead of new Notification('...') to push notifications. But when I tested it on the desktop chrome, it throws this error
registration.showNotification is not a function
Is the registration.showNotification only available on Android chrome but not on the desktop?
public componentDidMount(){
if ('serviceWorker' in navigator &&
(window.location.protocol === 'https:' || window.location.hostname === 'localhost')
) {
const registration = runtime.register();
registerEvents(registration, {
onInstalled: () => {
registration.showNotification('Title', { body: 'Body.'});
}
})
} else {
console.log('serviceWorker not available')
}
}
runtime.register() returns a JavaScript Promise, which is why you are getting a not a function error because Promises don't have a showNotification() method.
Instead, you'd have to chain a .then() callback to it in order to get the actual registration object (or use async/await, which is also cool).
runtime.register().then(registration => {
registration.showNotification(...);
})
Below solution worked for me. Can try.
The main root cause of .showNotification() not firing is service worker. Service worker is not getting registered. So it wont call registration.showNotification() method.
Add service-worker.js file to your project root directory
You can download service-worker.js file from Link
Use below code to register service worker and fire registration.showNotification() method.
const messaging = firebase.messaging();
messaging.onMessage(function (payload) {
console.log("Message received. ", payload);
NotisElem.innerHTML = NotisElem.innerHTML + JSON.stringify(payload);
//foreground notifications
if ('serviceWorker' in navigator) {
navigator.serviceWorker
.register('./service-worker.js', { scope: './' })
.then(function (registration) {
console.log("Service Worker Registered");
setTimeout(() => {
registration.showNotification(payload.data.title, {
body: payload.data.body,
data: payload.data.link
});
registration.update();
}, 100);
})
.catch(function (err) {
console.log("Service Worker Failed to Register", err);
})
}
});

Server worker being registered twice

I'm using FCM web notification service, when I am calling the register function:
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker.register('/sw.js').then(function(registration) {
// Registration was successful
console.log('ServiceWorker registration successful with scope: ', registration.scope);
}).catch(function(err) {
// registration failed :(
console.log('ServiceWorker registration failed: ', err);
});
});
}
The service worker is registered twice, one because of this function, and one by the FCM script. This is my service worker code:
importScripts('https://www.gstatic.com/firebasejs/3.5.2/firebase-app.js');
importScripts('https://www.gstatic.com/firebasejs/3.5.2/firebase-messaging.js');
'messagingSenderId': '<my senderid>'
});
const messaging = firebase.messaging();
messaging.setBackgroundMessageHandler(function (payload) {
self.addEventListener('notificationclick', function (event) {
event.notification.close();
var promise = new Promise(function (resolve) {
setTimeout(resolve, 1000);
}).then(function () {
return clients.openWindow(payload.data.locator);
});
event.waitUntil(promise);
});
var notificationTitle = payload.data.title;
var notificationOptions = {
body: payload.data.body,
icon: payload.data.icon
};
return self.registration.showNotification(notificationTitle,
notificationOptions);
});
One more thing, when I send test notifications, and I click the first message and it opens the URL correctly, but in the same instance of Chrome, all other messages I click open the URL of the first message. This problem does not happen on Firefox, just Chrome. I am using chrome version 55
With the firebase messaging SDK you don't need to call register.
If you call register, you can make the SDK use your service worker by calling useServiceWorker() (See: https://firebase.google.com/docs/reference/js/firebase.messaging.Messaging#useServiceWorker)
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker.register('/sw.js')
.then(function(registration) {
messaging.useServiceWorker(registration);
});
});
}
The reason the SDK registers the service worker for you is that it sets a scope that will prevent it from interfering with any other service workers you might have.
Regarding your second issue, are you sending different URLs or are they the same URLs?

Updating Web App User Interface when application is in background FCM

Am using FCM to handle notifications, it works fine up until when I need to update my UI from the firebase-messaging-sw.js when my web app is in the background.
My first question is: is it possible to update my web app UI in the background (When user is not focused on the web app) through a service worker
Secondly, if so, how? because I tried a couple of things and its not working, obviously am doing something wrong and when it does work, my web app is in the foreground. What am I doing wrong?
My codes are below.
my-firebase-service-sw.js
// [START initialize_firebase_in_sw]
// Give the service worker access to Firebase Messaging.
// Note that you can only use Firebase Messaging here, other Firebase
libraries
// are not available in the service worker.
importScripts('https://www.gstatic.com/firebasejs/4.1.1/firebase-app.js');
importScripts('https://www.gstatic.com/firebasejs/4.1.1/firebase-messaging.js');
// My Custom Service Worker Codes
var CACHE_NAME = 'assembly-v0.1.3.1';
var urlsToCache = [
'/',
'lib/vendors/bower_components/animate.css/animate.min.css',
'lib/vendors/bower_components/sweetalert/dist/sweetalert.css',
'lib/css/app_1.min.css',
'lib/css/app_2.min.css',
'lib/css/design.css'
];
var myserviceWorker;
var servicePort;
// Install Service Worker
self.addEventListener('install', function (event) {
console.log('installing...');
// Perform install steps
event.waitUntil(
caches.open(CACHE_NAME)
.then(function (cache) {
console.log('Opened cache');
return cache.addAll(urlsToCache);
})
);
console.log('installed...');
});
// Service Worker Active
self.addEventListener('activate', function (event) {
console.log('activated!');
// here you can run cache management
var cacheWhitelist = [CACHE_NAME];
event.waitUntil(
caches.keys().then(function (cacheNames) {
return Promise.all(
cacheNames.map(function (cacheName) {
if (cacheWhitelist.indexOf(cacheName) === -1) {
return caches.delete(cacheName);
}
})
);
})
);
});
self.addEventListener('fetch', function (event) {
event.respondWith(
caches.match(event.request)
.then(function (response) {
// Cache hit - return response
if (response) {
return response;
}
// IMPORTANT: Clone the request. A request is a stream and
// can only be consumed once. Since we are consuming this
// once by cache and once by the browser for fetch, we need
// to clone the response.
var fetchRequest = event.request.clone();
return fetch(fetchRequest).then(
function (response) {
// Check if we received a valid response
if (!response || response.status !== 200 || response.type !== 'basic') {
return response;
}
// IMPORTANT: Clone the response. A response is a stream
// and because we want the browser to consume the response
// as well as the cache consuming the response, we need
// to clone it so we have two streams.
var responseToCache = response.clone();
caches.open(CACHE_NAME)
.then(function (cache) {
cache.put(event.request, responseToCache);
});
return response;
}
);
})
);
});
self.addEventListener('message', function (event) {
console.log("SW Received Message: " + event.data);
// servicePort = event;
event.ports[0].postMessage("SW Replying Test Testing 4567!");
});
myserviceWorker = self;
// Initialize the Firebase app in the service worker by passing in the
// messagingSenderId.
firebase.initializeApp({
'messagingSenderId': '393093818386'
});
// Retrieve an instance of Firebase Messaging so that it can handle background
// messages.
const messaging = firebase.messaging();
// [END initialize_firebase_in_sw]
// If you would like to customize notifications that are received in the
// background (Web app is closed or not in browser focus) then you should
// implement this optional method.
// [START background_handler]
messaging.setBackgroundMessageHandler(function (payload) {
console.log('[firebase-messaging-sw.js] Received background message ', payload);
// Customize notification here
// send to client
console.log('Sending data to notification');
try {
myserviceWorker.clients.matchAll().then(function (clients) {
clients.forEach(function (client) {
console.log('sending to client ' + client);
client.postMessage({
"msg": "401",
"dta": payload.data
});
})
});
} catch (e) {
console.log(e);
}
const notificationTitle = payload.data.title;;
const notificationOptions = {
body: payload.data.body,
icon: payload.data.icon,
click_action: "value"
};
return self.registration.showNotification(notificationTitle,
notificationOptions);
});
// [END background_handler]
In my main javascript file, which receives the payload. it receives it when the application is in the foreground. My major concern and problem is receiving payload when the application is in the background, all activities on foreground works just fine.
It is possible to update the UI even your website is opening but unfocused.
Just add enable option includeUncontrolled when you get all client list.
Example:
messaging.setBackgroundMessageHandler(function (payload) {
console.log('[firebase-messaging-sw.js] Received background message ', payload);
self.clients.matchAll({includeUncontrolled: true}).then(function (clients) {
console.log(clients);
//you can see your main window client in this list.
clients.forEach(function(client) {
client.postMessage('YOUR_MESSAGE_HERE');
})
})
});
In your main page, just add listener for message from service worker.
Ex:
navigator.serviceWorker.addEventListener('message', function (event) {
console.log('event listener', event);
});
See Clients.matchAll() for more details.

Categories

Resources