progressive web app failed to work in offline when on live server - google-chrome-devtools

I am new to pwa and service worker, I have done the adding the manifest.json and service worker file in app. It is also showing the popup for save to home screen,
But it is not working in offline mode.
offline (using dev tool in chrome) below console error message.
The FetchEvent for "https://www.iservefinancial.com/" resulted in a network error response: the promise was rejected.
Promise.then (async)
(anonymous) # sw.js:23
sw.js:1 Uncaught (in promise) TypeError: Failed to fetch
I have also tried by changing the different urls
by trying using absolute urls like '/public/images/iserve-logo-blue.png'
and also tried by giving direct urls like in code snippet.
it is loading the resources in offline in localhost but failed to load on live server.(using chrome dev tool for offline test).
Code of the service worker file
const BASE_URL = "https://www.iservefinancial.com/";
self.addEventListener('install', function (event) {
console.log('sw installed');
event.waitUntil(
caches.open('static').then(function (cache) {
cache.addAll([
BASE_URL,
BASE_URL + 'public/js/jquery-3.2.1.min.js',
BASE_URL + 'public/images/logos/iserve-logo-blue.png',
'https://maxcdn.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css',
BASE_URL + 'public/css/fontawesomeak.css',
BASE_URL + 'public/css/questrial-font.css'
]);
})
);
});
self.addEventListener('activate', function () {
console.log('sw Activated');
});
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request)
.then(function (res) {
if (res) {
return res
} else {
return fetch(event.request)
}
})
);
});
The below code i have included in PHP view File
<link rel="manifest" href="<?php echo $baseurl; ?>public/scripts/manifest.json">
<script>
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js', {
scope: '.'
}).then(function () {
console.log('Service Worker Registered');
});
}
</script>
Manifest.json
{
"name": "iServeFinancial.Com",
"short_name": "iServeFinancial",
"start_url": "https://www.iservefinancial.com/",
"display": "standalone",
"orientation": "portrait",
"background_color": "#fff",
"theme_color": "#1770a3",
"icons": [{
"src": "https://www.iservefinancial.com/public/images/logos/iserve-logo-blue.png",
"sizes": "120x36",
"type": "image/png"
},
{
"src": "https://www.iservefinancial.com/public/images/logos/progressive.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "https://www.iservefinancial.com/public/images/logos/progressive1.png",
"sizes": "144x144",
"type": "image/png"
},
{
"src": "https://www.iservefinancial.com/public/images/logos/progressive2.png",
"sizes": "256x256",
"type": "image/png"
},
{
"src": "https://www.iservefinancial.com/public/images/logos/progressive2.png",
"sizes": "512x512",
"type": "image/png"
}
]
}
The page should work in online as well as offline.

It seems your service worker is not in the same level with your root path.
Your sw.js is registered in /iserve/, and you try to cache js file in /public/js/.
Also you are using http://localhost/iserve/sw.js which I think is not the same domain as your live website, should be change into /iserve/sw.js or /sw.js depends on your response header.
If you want to serve your sw.js in different folder, you should add Service-Worker-Allowed:'/' in your response header.
Also here is the code how I cache my response to make web offline.
self.addEventListener('fetch', function(event) {
event.respondWith(
caches.open(CURRENT_CACHES).then(function(cache) {
return fetch(event.request)
.then(function(response) {
cache.put(event.request, response.clone());
return response;
})
.catch(() =>
cache.match(event.request).then(function(response) {
return response || caches.match('/');
}),
);
}),
);
});

Related

Why does my PWA works on my phone but not on others?

Good day, I'm currently working on PWAs (Progressive Web Apps), and I wanted to make a try:
Here is my app script:
if( 'serviceWorker' in navigator ) {
navigator.serviceWorker.register( '/service-worker.js' )
.then( ( reg ) => logServe( 'service worker registred', reg ) )
.catch( ( err ) => logServe( 'service worker not registred', err ) );
}
Here is my manifest:
{
"name": "WebApp",
"short_name": "PWA",
"description": "Progressive Web App",
"start_url": "/",
"icons": [
{
"src": "icon/icon-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any"
},
{
"src": "icon/icon-256.png",
"sizes": "256x256",
"type": "image/png",
"purpose": "any"
},
{
"src": "icon/icon-192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any"
},
{
"src": "icon/icon-96.png",
"sizes": "96x96",
"type": "image/png",
"purpose": "any"
}
],
"theme_color": "#000000",
"background_color": "#FFFFFF",
"display": "standalone",
"orientation": "portrait"
}
And here is my service worker:
self.addEventListener( 'install', evt => {
logServe( 'service worker has been installed' );
} );
self.addEventListener( 'activate', evt => {
logServe( 'service worker has been activated' );
} );
self.addEventListener( 'fetch', evt => {
logServe( 'fetch event', evt );
} );
To test it, I tried:
On my computer (with edge/chrome) -> I can see the default install button in the address bar, and allow me to install it
On my samsung phone through ngrok and on chrome -> It shows the button* I've set up to install the app and allow me to install it
Here is the script for my custom installation button*:
let deferredPrompt;
const addBtn = document.getElementsByTagName( 'a' )[0];
addBtn.style.display = 'none';
window.addEventListener( 'beforeinstallprompt', ( e ) => {
// Prevent Chrome 67 and earlier from automatically showing the prompt
e.preventDefault();
// Stash the event so it can be triggered later.
deferredPrompt = e;
// Update UI to notify the user they can add to home screen
addBtn.style.display = 'block';
addBtn.addEventListener( 'click', ( e ) => {
// hide our user interface that shows our A2HS button
addBtn.style.display = 'none';
// Show the prompt
deferredPrompt.prompt();
// Wait for the user to respond to the prompt
deferredPrompt.userChoice.then( ( choiceResult ) => {
if( choiceResult.outcome === 'accepted' ) {
console.log( 'User accepted the A2HS prompt' );
} else {
console.log( 'User dismissed the A2HS prompt' );
}
deferredPrompt = null;
} );
} );
} );
But as soon as I try on another device, it fails, I see the "service worker registered", but that's it.
So I checked on a PWA framework/validator (PWA Builder), and this validator told me I got all that is required to publish my PWA.
Any idea from where it's coming from?
EDIT
After sorting out my Lighthouse issue, I performed few tests and tried correcting some pointS, but I still have 2 issues I cannot solve:
a HTTP redirecting issue, I tried with the HTML tag <link rel="canonical" href="my https domain url"/> or with the following HTACCESS:
#RewriteEngine on
#ReWriteCond %{SERVER_PORT} !^443$
#RewriteRule ^/(.*) https://%{HTTP_HOST}/$1 [NC,R,L]
but I still have this issue (if possible, I would like to avoid making server set up to solve this.
a maskable issue on Chrome (even if I added maskables icons)
Here is my new icons definition:
{
"src": "icon/any/x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any"
},
{
"src": "icon/any/x384.png",
"sizes": "384x384",
"type": "image/png",
"purpose": "any"
},
{
"src": "icon/any/x192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any"
},
{
"src": "icon/any/x128.png",
"sizes": "128x128",
"type": "image/png",
"purpose": "any"
},
{
"src": "icon/any/x96.png",
"sizes": "96x96",
"type": "image/png",
"purpose": "any"
},
{
"src": "icon/any/x72.png",
"sizes": "72x72",
"type": "image/png",
"purpose": "any"
},
{
"src": "icon/any/x48.png",
"sizes": "48x48",
"type": "image/png",
"purpose": "any"
},
{
"src": "icon/maskable/x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "icon/maskable/x384.png",
"sizes": "384x384",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "icon/maskable/x192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "icon/maskable/x128.png",
"sizes": "128x128",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "icon/maskable/x96.png",
"sizes": "96x96",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "icon/maskable/x72.png",
"sizes": "72x72",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "icon/maskable/x48.png",
"sizes": "48x48",
"type": "image/png",
"purpose": "maskable"
}
Here is what happen on Edge - Chrome when doing a Lighthouse test (Edge vs Chrome)
lighthouse edge and chrome

Sharepoint Rest API - Apply filter on expanded fields - Status 400

I am using Sharepoint rest API to get specific files in a group of folders. For this, I am applying filter on the expanded field. The problem is when I apply filter, it says the "Field or property does not exist"
I've tried to get the data without applying filter and it's coming correctly. Also, I am able to apply filter on the fields which are not under the expand parameter.
Below code is working in postman:
https://sp.foo.net/sites/spdsdfrn/_api/web/GetFolderByServerRelativeUrl('Shared Documents/abc/2019')/Folders?$expand=Files&$select=Files/Name&$filter=Files/Name eq 'abc.xlsx'
Below is the relevant part of the output:
{
"d": {
"results": [
{
"__metadata": {
"id": "https://sp.foo.net/sites/spdsdfrn/_api/web/GetFolderByServerRelativeUrl('Shared Documents/abc/2019/folder1')",
"uri": "https://sp.foo.net/sites/spdsdfrn/_api/web/GetFolderByServerRelativeUrl('Shared Documents/abc/2019/folder1')",
"type": "SP.Folder"
},
"Files": {
"results": [
{
"__metadata": {
"id": "https://sp.foo.net/sites/spdsdfrn/_api/web/GetFolderByServerRelativeUrl('Shared Documents/abc/2019/folder1/abc.xlsx')",
"uri": "https://sp.foo.net/sites/spdsdfrn/_api/web/GetFolderByServerRelativeUrl('Shared Documents/abc/2019/folder1/abc.xlsx')",
"type": "SP.File"
},
"Name": "abc.xlsx"
},
{
"__metadata": {
"id": "https://sp.foo.net/sites/spdsdfrn/_api/web/GetFolderByServerRelativeUrl('Shared Documents/abc/2019/folder1/def.xlsx')",
"uri": "https://sp.foo.net/sites/spdsdfrn/_api/web/GetFolderByServerRelativeUrl('Shared Documents/abc/2019/folder1/def.xlsx')",
"type": "SP.File"
},
"Name": "def.xlsx"
}
]
}
},
.........
.........
..........
Below code is not working in postman:
https://sp.foo.net/sites/spdsdfrn/_api/web/GetFolderByServerRelativeUrl('Shared Documents/abc/2019')/Folders?$expand=Files&$select=Files/Name&$filter=Files/Name eq 'abc.xlsx'
Below is the error output I am getting with status code 400 (bad request):
{
"error": {
"code": "-1, Microsoft.SharePoint.Client.InvalidClientQueryException",
"message": {
"lang": "en-US",
"value": "Field or property \"Name\" does not exist."
}
}
}
I've seen many solutions on the internet and they suggest that it should work in this way. Also, I've seen to check the internal names as they might differ but it's same "Name" in this case.
Is it some bug or I am missing something?
This looks like a typo:
"Field or property \"Names\" does not exist."
As your URL references "Name" not "Names".
Your second URL does not have the file type (.xlsx)
&$filter=Files/Name eq 'abc'
Otherwise...
Your URL includes "/folder". This returns a list of folders. Are you looking for a file in a particular folder, or all files by that name in any folder?
This will return a file in a particular folder:
https://sp.foo.net/sites/spdsdfrn/_api/web/GetFolderByServerRelativeUrl('Shared Documents/abc/2019')/files?$select=Name&$filter=Name eq 'abc.xlsx'
Technically speaking... It's a "server relative", not "site relative" URL. But starting with the library name seems to work.
https://sp.foo.net/sites/spdsdfrn/_api/web/GetFolderByServerRelativeUrl('Shared Documents/abc/2019')/Folders
probably should be:
https://sp.foo.net/sites/spdsdfrn/_api/web/GetFolderByServerRelativeUrl('/sites/spdsdfrn/Shared Documents/abc/2019')/Folders
You can use GetItems method in combination with setting FolderServerRelativeUrl property and Scope.
Example code:
<script src="https://code.jquery.com/jquery-1.12.4.min.js" type="text/javascript"></script>
<script type="text/javascript">
$(function () {
var fileName="abc.xlsx";
var libraryTitle="Documents";
var folderRelativeUrl="Shared Documents/abc/2019";
var viewXml = "<View Scope='RecursiveAll'><Query><Where><Eq><FieldRef Name='FileLeafRef'/><Value Type='File'>"+fileName+"</Value></Eq></Where></Query></View>";
var url = _spPageContextInfo.webAbsoluteUrl + "/_api/web/lists/getbytitle('"+libraryTitle+"')/getitems?$select=*,FileDirRef,FileRef";
var query = {
'query' : {
'__metadata': { 'type': 'SP.CamlQuery' },
'ViewXml' : viewXml,
'FolderServerRelativeUrl': folderRelativeUrl
}
};
$.ajax({
url: url,
method: "POST",
data: JSON.stringify(query),
headers: {
"X-RequestDigest": $("#__REQUESTDIGEST").val(),
"Accept": "application/json; odata=verbose",
"content-type": "application/json; odata=verbose"
},
success: function (data) {
alert(JSON.stringify(data.d.results));
},
error: function (err) {
alert(JSON.stringify(err));
}
});
});
</script>
Or we can use the REST API below.
/_api/web/lists/getbytitle('Documents')/items?$select=File&$expand=File&$filter=FileLeafRef eq 'abc.xlsx'

Two bindings works by calling backend. One binding won't call backend

Can you help me solve this mystery? I got the walkthrough example in step 10 and adapted it so I can create a simple binding to an OData service.
Let me share the main files:
Component.js
sap.ui.define([
"sap/ui/core/UIComponent",
"sap/ui/model/json/JSONModel"
], function(UIComponent, JSONModel) {
"use strict";
return UIComponent.extend("BasicMessages.Component", {
metadata: {
manifest: "json"
},
init: function() {
// call the init function of the parent
UIComponent.prototype.init.apply(this, arguments);
// set data model
var oData = {
recipient: {
name: "World"
}
};
var oModel = new JSONModel(oData);
this.setModel(oModel, "json");
},
});
});
Manifest.json (hid the service URI, but it's properly configured)
{
"_version": "1.8.0",
"sap.app": {
"id": "BasicMessages",
"type": "application",
"i18n": "i18n/i18n.properties",
"title": "{{appTitle}}",
"description": "{{appDescription}}",
"applicationVersion": {
"version": "1.0.0"
},
"dataSources": {
"mainService": {
"uri": "hidden_service_uri",
"type": "OData",
"settings": {
"odataVersion": "2.0",
"localUri": "localService/metadata.xml"
}
}
}
},
"sap.ui": {
"technology": "UI5",
"deviceTypes": {
"desktop": true,
"tablet": true,
"phone": true
}
},
"sap.ui5": {
"rootView": {
"viewName": "BasicMessages.view.App",
"type": "XML",
"async": true,
"id": "app"
},
"dependencies": {
"minUI5Version": "1.30",
"libs": {
"sap.m": {}
}
},
"models": {
"i18n": {
"type": "sap.ui.model.resource.ResourceModel",
"settings": {
"bundleName": "BasicMessages.i18n.i18n"
}
},
"": {
"dataSource": "mainService",
"settings": {
"defaultUpdateMethod": "PUT",
"useBatch": false,
"metadataUrlParams": {
"sap-documentation": "heading"
},
"defaultBindingMode": "TwoWay"
}
}
}
}
}
App.controller.js
sap.ui.define([
"sap/ui/core/mvc/Controller",
"sap/m/MessageToast"
], function(Controller, MessageToast) {
"use strict";
return Controller.extend("BasicMessages.controller.App", {
onInit: function() {
this.getView().setModel(this.getOwnerComponent().getModel());
},
onShowHello: function() {
// read msg from i18n model
var oBundle = this.getView().getModel("i18n").getResourceBundle();
var sRecipient = this.getView().getModel("json").getProperty("/recipient/name");
var sMsg = oBundle.getText("helloMsg", [sRecipient]);
// show message
MessageToast.show(sMsg);
},
});
});
App.view.xml (this is the tricky file)
<mvc:View controllerName="BasicMessages.controller.App"
xmlns:mvc="sap.ui.core.mvc"
xmlns="sap.m">
<Button text="{i18n>showHelloButtonText}" press="onShowHello" />
<Input value="{json>/recipient/name}"
description="Hello {json>/recipient/name}"
valueLiveUpdate="true" width="60%"
/>
<Text text="{/Caixas(Id='0001',InicioValidade='20180712193002')/Descricao}" />
<List items="{/Caixas}">
<StandardListItem title="{Descricao}" />
</List>
</mvc:View>
If I use the view file exactly like that, it calls the OData service on the backend and retrieve the list and the text element contents.
link to screenshot:
If I comment the list lines, the OData service won't be called and the text element won't be filled with the text.
link to screenshot:
Why does this happen???
Got the answer on SAP Docs
Note: Requests to the back end are triggered by list bindings (ODataListBinding), element bindings (ODataContextBinding), and CRUD functions provided by the ODataModel. Property bindings (ODataPropertyBindings) do not trigger requests.
I think, Rafael explained why this happens (your question), now to answer the unasked question "howto make this work":
You would need to bind the Text control to the entitiy and the text property to the Descricio attribute, basically something like that:
<Text binding="{/Caixas(Id='0001',InicioValidade='20180712193002')" text="{Descricao}" />
(sorry, no ui5 runtime environment at hand, so I didnt actually run this)

Can a templating engine such as EJS be used in a PWA?

I am learning about writing Progressive Web Apps and all of the examples use html files. I'd prefer to use EJS with a node server. Is it possible to cache the ejs so it can be used locally?
Short answer: Yes.
A service worker is going to cache a response for a given URL, so it is irrelevant of whether you will be using EJS or any other templating engine.
Of course, you need to see whether you would use the service worker to cache the template file (ex: templates/mytemplate.ejs) or the rendered HTML. If you cache the output HTML, then it will be returned from the cache and not rendered dynamically from the template on successive requests..
This may help you
Service-worker.js
const cacheName = 'pwa-demo-v1';
const filesToCache = [
'/',
'/index.ejs',
'/partials/header.ejs',
'/partials/footer.ejs',
'/css/style.css',
'/js/app.js',
'/js/menu.js',
'/images/refresh.svg',
'/images/pwa.png'
]
// Install Service Worker
self.addEventListener('install', function(event){
console.log('Service Worker: Installing....');
event.waitUntil(
// Open the Cache
caches.open(cacheName).then(function(cache) {
console.log('Service Worker: Caching App Shell at the moment......');
// Add Files to the Cache
return cache.addAll(filesToCache);
})
);
})
// Fired when the Service Worker starts up
self.addEventListener('activate', function(event) {
console.log('Service Worker: Activating....');
event.waitUntil(
caches.keys().then(function(cacheNames) {
return Promise.all(cacheNames.map(function(key) {
if( key !== cacheName) {
console.log('Service Worker: Removing Old Cache', key);
return caches.delete(key);
}
}));
})
);
return self.clients.claim();
});
self.clients.claim()
self.addEventListener('fetch', function(event) {
console.log('Service Worker: Fetch', event.request.url);
console.log("Url", event.request.url);
event.respondWith(
caches.match(event.request).then(function(response) {
return response || fetch(event.request);
})
);
});
manifest.json file:
{
"name": "Progressive Web App - Demo",
"short_name": "PWADemo",
"description": "Progressive Web Apps - demo application",
"start_url": "/",
"display": "standalone",
"background_color": "#4c4849",
"theme_color": "#4c4849",
"icons": [
{
"src": "/images/192x192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "/images/168x168.png",
"type": "image/png",
"sizes": "168x168"
},
{
"src": "/images/144x144.png",
"type": "image/png",
"sizes": "144x144"
},
{
"src": "/images/96x96.png",
"type": "image/png",
"sizes": "96x96"
},
{
"src": "/images/72x72.png",
"type": "image/png",
"sizes": "72x72"
},
{
"src": "/images/48x48.png",
"type": "image/png",
"sizes": "48x48"
}
],
"gcm_sender_id": "1001123745362"
}
for more details please check my repository - https://github.com/deepakgd/PWA-Demo
I think the service worker only caches the files within its scope
- It only has access to the files on its folder or “below”. Your service worker should be in your public folder not in your views folder. And when caching, you should cache all static files in your folder, then cache the url path that is rendering your views files.
Example.. '/about',
'/404',
'/admin/signup',
'/admin/signin',
That's all. I hope it helps.

Broccoli asset rev fingerprint urls in json file

My rails app and index.html file are hosted on heroku and the rest of the assets are on s3, there's a manifest.json file that is required in order to enable mobile web app mode for chrome on android that looks like:
{
"short_name": "Kinlan's Amaze App",
"name": "Kinlan's Amazing Application ++",
"icons": [
{
"src": "launcher-icon-2x.png",
"sizes": "96x96",
"type": "image/png"
},
{
"src": "launcher-icon-3x.png",
"sizes": "144x144",
"type": "image/png"
},
{
"src": "launcher-icon-4x.png",
"sizes": "192x192",
"type": "image/png"
}
],
"start_url": "/index.html",
"display": "standalone",
"orientation": "landscape"
}
In my brocfile.js I have included the json extension to the list of fingerprintable files.
The actual file gets fingerprinted but the contents such as "src": "launcher-icon-3x.png" do not.
Anyway to do this or do I need to make an in-Repo addon to handle the creation of the file?
After looking through the source the following can be done by adding json to the replaceExtensions option:
var app = new EmberApp({
fingerprint: {
prepend: fingerprint,
extensions: ['js', 'css', 'png', 'jpg', 'gif', 'svg', 'json'],
replaceExtensions: ['html', 'css', 'js', 'json']
}
});