Why does this Service Worker manifest changes request URL for icon images - service-worker

I have a JavaScript file with the service worker logic content request URL nested in a directory. I have the ability to and have configured the web server to serve that file with HTTP header Service-Worker-Allowed as "/".
The manifest file is also located in a directory reflecting the request URL. However, the scope and images are set assuming the same request URL is set to the main scope.
The actual request URL I see is set relative to the directory from the manifest, not the root directory I want to start from.
So this is the timeline of the browser:
First, request the website which returns the index file: https://localhost:3000.
index.html
<!DOCTYPE html>
<html lang="en-US">
<head>
<link rel="manifest" href="json/manifest.json" async>
<script id="js-webapp" type="module" src="js/webapp.js" defer async></script>
</head>
<body>
</body>
</html>
Second, it parses that and requests the main app logic: https://localhost:3000/js/webapp.js.
webapp.js
window.addEventListener('load', function (event) {
const path = '/js/sw.js'
if ('serviceWorker' in navigator) {
console.log('Service Worker present')
navigator.serviceWorker.register(path, { scope: './'})
.then((registration) => {
console.log('Service Worker Registered', registration)
})
.catch((err) => {
console.log('Service Worker Failed to Register: ', err)
})
}
})
Third, my app logic determines whether or not to load a service worker file based on the browser configuration: https://localhost:3000/js/sw.js. (This is the HTTP header that has response key-val "service-worker-allowed" and "/".
sw.js
const cacheName = 'v1.0.0'
const cacheFiles = [
'./manifest.json',
'./css/theme.css',
'./js/webapp.js',
'./img/logo.ico'
]
self.addEventListener('install', (event) => {
console.log('Installed')
event.waitUntil(
caches.open(cacheName).then((cache) => {
console.log('Caching cacheFiles')
return cache.addAll(cacheFiles)
})
)
})
Next, the service worker requests the manifest to cache the file: https://localhost:3000/json/manifest.json. (This was loaded on the main index file but nothing is done to this file without the service worker logic being triggered.)
manifest.json
{
"name": "Web App",
"short_name": "",
"start_url": "/",
"scope": "/",
"display": "standalone",
"display": "fullscreen",
"theme_color": "#404040",
"background_color": "#404040",
"icons": [
{
"src": "img/ic_launcher_48.png",
"sizes": "48x48",
"type": "image/png"
},
...
]
}
Following this logic, I would expect the URL request to be https://localhost:3000/img/ic_launcher_48.png. However, the request URL I see being performed by the browser is https://localhost:3000/json/img/ic_launcher_48.png.
What am I doing wrong if I already set to the service worker allowed HTTP in the header when I served the main file that installs, activates, and fetches service worker logic?
All other content that is loaded from other JavaScript, or HTML files load correctly.

the icons src is relative to where the manifest file is, since your manifest is at /json then the browser is looking for the icon at /json/img

Related

How to serve apple-app-site-association file on /apple-app-site-association page in ReactJS

I'm having a lot of trouble with serving a apple-app-site-association file in ReactJS project.
I've searched a lot of GitHub issues pages (for ReactJS, Gatsby, and Angular), forums, and a lot of online communities, and it seems that I can't find a solution for this.
What I've tried is:
Adding the file into public/.well-known folder.
Adding a separate route via react-router on path "/apple-app-site-association" and returning an tag with file
Adding <link rel="apple-app-site-association" href="%PUBLIC_URL%/.well-known/apple-app-site-association"> into public/index.html
Testing through the "aasa-validator" returns:
Your file's 'content-type' header was not found or was not recognized.
Keep in mind that:
The apple-app-site-association JSON file must not have a .json file extension.
It has to be on "/apple-app-site-association" or "./well-known/apple-app-site-association" links on the website.
I can't use a redirect to another page/link.
Thanks in advance!
Ps. If it helps, I'm using a Heroku for deployment.
For NextJs
As discussed here, it is possible to server apple-app-site-association from NextJs.
Next.js uses the extension to automatically detect the content-type of the file. Since the file is extensionless, it's served as binary content—this should be compatible with Apple's verification.
If Apple requires specific headers, you can overwrite this type:
// next.config.js
module.exports = {
experimental: {
headers() {
return [
{
source: "/.well-known/apple-app-site-association",
headers: [{ key: "content-type", value: "application/json" }]
}
];
}
}
};
You can serve the file using React Router.
Put this in your index.js or App.js:
const reload = () => window.location.reload();
<Route path="/apple-app-site-association" onEnter={reload} />
More details here:
https://brainbank.cc/jamie/lessons/programming-react/serve-static-file-txt-html-via-react-router
several days have passed and I didn't find an answer or solution. (And I really hate to see an unanswered stackoverflow question when I'm searching for a solution)
I've talked to a lot of people and pretty much everyone said this is impossible. Reason for that is that ReactJS is unable to return JSON type of response upon client sending a GET request.
So we transfered the file onto the back-end side which is working perfectly.
tl;dr It's not possible.
For CRA:
The file apple-app-site-association should be added to the public folder or public/.well-known folder. This alone won't make it work. Because it is served with a content-type of application/octet-stream. To fix it, we should use a solution provided by the hosting provider.
For Firebase:
I am using Firebase for hosting my CRA app. So, for firebase, it is possible to specify the response headers for the path. The following gist shows how to do this. Make sure that the appAssociation should also be set to prevent firebase dynamic links from serving some other AASA file.
firebase.json config
{
"hosting": {
"public": "public",
"headers": [
{
"source": "/.well-known/apple-app-site-association",
"headers": [
{
"key": "Content-Type",
"value": "application/json"
}
]
}
],
"appAssociation": "NONE"
}
}
If you're using Cloudfront in AWS you can create a custom Response Headers Policy:
We serve our CRA app using the npm serve CLI package. This is what we did to resolve the issue of serving our extension-less apple-app-site-association file.
The fix for this is to create the apple-app-site-association as apple-app-site-association.json inside your public/.well-known directory and then create a rewrite rule inside of a file in your public directory called serve.json. The contents of that file should look like this.
{
"rewrites": [
{
"source": "!.well-known/**",
"destination": "index.html"
},
{
"source": ".well-known/apple-app-site-association",
"destination": ".well-known/apple-app-site-association.json"
}
]
}
Then when you start your static file server, you don't use the -s or --single argument. You start it using the following command.
serve -c serve.json
The first item will send all requests that don't exist inside of .well-known to index.html, and the second rewrite will send requests for that specific URL to the apple-app-site-association.json file and assign a content-type: application/json header to it. All other files with file extensions should be served as normal, ignoring any rewrite rules.
Disclaimer: This is only applicable if you have an Nginx implementation.
I got stuck on this and couldn't figure out a way to add the apple site association file to the react code in any way.
However, I was able to solve the issue by looking outside the react box and inside my server configuration box i.e. Nginx.
Here are the steps:
In the server store the AASA or apple-app-site-association.json file in a particular location - I chose to store it at (had to create two certifications and ios directories)
/var/www/certificates/ios/
Now go to the sites-available folder
cd /etc/nginx/sites-available/
Here, you will find the file with your web app's Nginx configuration information.
Let's say, the name of my file was example-web-app, so open the file using vim with sudo privileges:
sudo vim mayank-web-app
Now inside this file, you need to find
location / {
Just above this line paste this code block
location /apple-app-site-association {
alias /var/www/certificates/ios/;
index apple-app-site-association.json
autoindex on; }
Now save the file and exit, by pressing esc and typing
wq!
Now check if the changes are valid by typing
sudo nginx -t
You should get this as output
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
After this you will have to go to the sites-enabled directory
cd ../sites-enabled
9. Update the symlink here
sudo ln -s /etc/nginx/sites-available/example-web-app .
Now reload the nginx service
sudo service nginx reload
Now you can check this with two methods:
a. https://branch.io/resources/aasa-validator/#resultsbox.
b. https://example.com/apple-app-site-association
I has this problem using react/redux and Next.js
this is how I solved the issue
in my /static folder I put my apple-app-site-association
{
"webcredentials": {
"apps": ["12345431.com.app"]
},
"applinks": {
"apps": [],
"details": [
{
"appID": "12345431.com.app",
"paths": [ "*"]
}
}
]
}
}
I created a component called Apple AppleAssociation.jsx in that file I have
import React from 'react'
function AppleAssociation() {
return (
//path to apple app site association file
<>
<link rel="apple-app-site-association file" href="/static/apple-app-site-association" />
</>
)
}
export default AppleAssociation
and in my _app.jsx file I import that into my head
import React from 'react'
import Head from 'next/head'
import withRedux from 'next-redux-wrapper'
import App, { Container as NextContainer } from 'next/app'
import { Provider as ReduxProvider } from 'react-redux'
import {
AppleAssociation,
} from '../components/head'
import makeStore from '../lib/store'
class CustomApp extends App {
static async getInitialProps({ Component}) {
let pageProps = {}
return {
pageProps,
}
}
componentDidMount() {
}
render() {
const { Component, pageProps, session, store } = this.props
return (
<NextContainer>
<Head>
<meta
name="viewport"
content="width=device-width, initial-scale=1, minimal-ui"
/>
<AppleAssociation />
</Head>
<ReduxProvider store={store}>
<NextSeo config={seo} />
<Component {...pageProps} />
</ReduxProvider>
</NextContainer>
)
}
}
export default withRedux(makeStore)(CustomApp)
you can test this by using https://branch.io/resources/aasa-validator/#resultsbox
If you have any other redirects / rewrite rules make sure the apple-app-site-association re-write is placed before others.
For example this is with AWS Amplify for React JS:
[
{
"source": "/.well-known/apple-app-site-association",
"target": "/.well-known/apple-app-site-association.json",
"status": "200",
"condition": null
},
{
"source": "</^[^.]+$|\\.(?!(css|gif|ico|jpg|js|png|txt|svg|woff|ttf|map|json)$)([^.]+$)/>",
"target": "/index.html",
"status": "200",
"condition": null
}
]
with the apple-app-site-association file placed as a JSON file here:
/public/.well-known/apple-app-site-association.json

PWA 'Hello World' keeps giving me a service worker error on Manifest Tab of dev tools - can't install as an app

I have read all I can on service workers and PWA's and I can't figure out what I am doing wrong, I think it must be my virtual directory on IIS or something but I have tried changing the scope of the service worker registration, changed the start_url in the manifest, etc., and nothing seems to work. The error I get is in the dev tools on the Application->Manifest tab:
"Site cannot be installed: no matching service worker detected. You may need to reload the page, or check that the service worker for the current page also controls the start URL from the manifest"
I have created a virgin application on IIS called TestWeb and can browse to a 'hello world' page at
https://localhost/TestWeb/
All the js files and the manifest are in the root of the site. In the header of the page, I have:
<link rel="manifest" href="Manifest.json">
and that points to a json file in the root of the site like so:
{
"dir": "ltr",
"lang": "en",
"name": "xxxx",
"scope": "/",
"display": "standalone",
"start_url": ".",
"short_name": "xxxx",
"theme_color": "#ffffff",
"description": "",
"orientation": "any",
"background_color": "transparent",
"related_applications": [],
"prefer_related_applications": false,
"icons": [
{
"src": "Images/AppIcons/windows10/Square71x71Logo.scale-400.png",
"sizes": "284x284"
},
etc...
For the scope of the manifest, I have tried "./", "/", "/TestWeb/", "/TestWeb", etc.. When the document is ready, I call this function:
if ("serviceWorker" in navigator) {
if (navigator.serviceWorker.controller) {
console.log("Active service worker found, no need to register");
} else {
// Register the service worker
navigator.serviceWorker
.register("ServiceWorkerRegister.js", {
scope: "./"
})
.then(function (reg) {
console.log("Service worker has been registered for scope: " + reg.scope);
});
//.catch(err => console.log(err));
}
}
}
For the scope here, I have tried "./", "/", "/TestWeb/", "/TestWeb", etc..
The service worker successfully activates and is running but there is no 'add' button on the browser, and going to the Application->Manfiest in dev tools, I get the error saying no matching serivce worker. I have tried refreshing multiple times.
I am stumped, does anyone know of something else to try?
To get installation prompt your source code must have this (ServiceWorkerRegister.js) file with at least an event listener for fetch, and other installable requirements too.

how do I include files that sw-precache misses

Context
I'm using parcel-plugin-sw-precache which wraps around sw-precache to make it work with Parcel.js. Everything was working as expected, and I have been testing my offline app.
Problem
I added react-pdf.js into my project, one of the dependencies for this library doesn't get added into the service worker when it is generated by the sw-precache. I know this because the file "pdf.worker.entry.7ce4fb6a.js" gives a 304 error when I switch to offline mode.
What I have tried
I'm trying to add the file manually to the package.json parcel-plugin-sw-precache config using this code:
"sw-precache": {
"maximumFileSizeToCacheInBytes": 10485760,
"staticFileGlobs": [
"/pdf.worker.entry.7ce4fb6a.js"
]
},
I'm not sure if the file path should be relative to package.json or relative the generated service worker. In anycase, the manually specified file doesn't get added to generate services worker as I would expect. As seen below.
self.__precacheManifest = [{
"url": "index.html",
"revision": "ac5ace7a43a0fef7ae65fd3119987d1f"
}, {
"url": "castly.e31bb0bc.css",
"revision": "657409f7159cb857b9409c44a15e653f"
}, {
"url": "castly.e31bb0bc.js",
"revision": "018d4664d809ec19d167421b359823ad"
}, {
"url": "/",
"revision": "af5513bb330deae3098ab289d69a40c7"
}]
The question
If the sw-precache or parcel-plugin-sw-precache seem to be missing some files, how can I make sure they get added to the generated service worker?
In my exploration for an answer. I gave up on using parcel-plugin-sw-precache and instead I switched to using workbox. If you are interested in creating an offline app with Parcel.js. Then I recommend Workbox as it is the next generation of sw-precache.
There is how I got it working:
Learning
Learn what Workbox is and what is does with this code lab.
Implimenting
1) Install the Workbox CLI globally.
2) create a placeholding service worker in the root directory. e.g. sw_shell.js
- The shell is a holding file. The Workbox wizard will pick it up and generate a
new sw.js file automatically.
3) Add to the sw_config.js the following code:
importScripts("https://storage.googleapis.com/workbox-cdn/releases/3.6.3/workbox-sw.js");
if (workbox) {
workbox.skipWaiting();
workbox.clientsClaim();
workbox.precaching.suppressWarnings();
// The next line came from the code lab
workbox.precaching.precacheAndRoute([]);
workbox.routing.registerNavigationRoute("/index.html");
} else {
console.log(`Boo! Workbox didn't load đŸ˜¬`);
}
4) Run this code from a command line opened in your project's root directory.
workbox wizard --injectManifest
5) Follow the steps in the wizard. For dev purposes point the "root web app" to your parcel dist folder. Workbox does it's magic and picks up those files to be hashed into a new sw.js file.
6) The wizard will ask for your existing sw.js file. In my case I use the sw_shell.js.
a:Workbox picks up the sw_shell.js.
c:Generates as new sw.js file in a location specfied when running the wizard, and injects the files to run offline.
In my case I let the new sw.js generate in my root folder because Parcel picks it up automatically as per the script in my index.js.
'use strict';
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker.register('sw.js').then(function(reg) {
console.log('Worker registration started')
reg.onupdatefound = function() {
console.log('update found')
var installingWorker = reg.installing;
installingWorker.onstatechange = function() {
console.log('installing worker')
switch (installingWorker.state) {
case 'installed':
if (navigator.serviceWorker.controller) {
console.log('New or updated content is available.');
} else {
console.log('Content is now available offline!');
}
break;
case 'redundant':
console.error('The installing service worker became redundant.');
break;
}
};
};
}).catch(function(e) {
console.error('Error during service worker registration:', e);
});
});
}
7) Add workbox injectManifest to your package.json to make sure Workbox picks up any changes to your files:
"scripts": {
"start": "parcel index.html workbox injectManifest"
}
Let me know if you want to know more about this. There is a video here that helped me a little bit also.

Why does my progressive web app open in a browser when I have fullscreen specified in my app manifest?

When I install my web app to homescreen on Android without a service worker, everything works as expected. I can click my home screen icon and my app launches with a splash screen and then I see my app in fullscreen mode. When I add in the service worker registration code and install to home screen, my app launches in a browser window and seems to ignore my manifest file.
Something to note is that without the service worker my app only has an icon on the homescreen. with the service worker code it becomes fully installed with the "new improved add to home screen" and there is an icon on my home screen as well as in my installed apps panel.
Here is my code:
web-app.html:
<!DOCTYPE html>
<html>
<head>
<title>Web App</title>
<meta name = "viewport" content = "user-scalable=no, width=device-width">
<meta name = "mobile-web-app-capable" content = "yes">
<link href = "manifest.json" rel = "manifest">
<link href = "web-app.css" rel = "stylesheet" type = "text/css">
<link href = "web-app.png" rel = "icon" type = "image/png">
</head>
<body>
<h1>Android Web App!</h1>
<img src = "/web-app.png">
<p>This page can be viewed in any browser, but it can also work in a web app! If you are viewing this page in a full screened webview on your mobile device, you are looking at a fully functional web app! You can use this technology to better connect with your users or create a full screen mobile experience for your HTML5 games!</p>
<script type = "text/javascript">
navigator.serviceWorker.register("web-app-service.js");
</script>
</body>
</html>
manifest.json:
{
"author": "PoP Vlog",
"background_color": "#ffffff",
"description": "Progressive Web App Example with Offline Mode",
"display": "fullscreen",
"icons": [{
"src": "/web-app.png",
"sizes": "192x192",
"type": "image/png"
}],
"lang":"en",
"manifest_version": 2,
"name": "Web App",
"orientation": "portrait",
"scope":"/",
"short_name": "Web App",
"start_url": "/",
"theme_color": "#ffffff",
"version": "0.2"
}
web-app-service.js:
self.addEventListener("install", function(event) {
event.waitUntil(caches.open("web-app").then(function(cache) {
return cache.addAll([ "/", "/web-app.html", "/web-app.css", "/web-app.png"]).then(function() {
self.skipWaiting();
});
}));
});
self.addEventListener("activate", function(event) {
event.waitUntil(self.clients.claim());
});
self.addEventListener("fetch", function(event) {
event.respondWith(caches.match(event.request).then(function(response) {
return response || fetch(event.request);
}));
});
I couldn't find a lot of documentation on this, but clearly the behavior changes when I remove the service worker registration code from my html document. I suspect that the problem lies in my web-app-service.js file.
EDIT: 11/01/2017
When I completely comment out my fetch event listener, my app works as expected, but add to home screen only installs an icon on my home screen. When I add to home screen with the fetch event listener, I get a full install of my web app into my apps panel and my app only opens in a browser window with full url bar and everything. This problem has something to do with adding the fetch capability to my service worker.
EDIT 11/08/2017
I have discovered that I only have this problem on my Node JS HTTPS test server when accessing my web app through a local IP address. The web app works fine when I run it from my Github Pages site. This leads me to believe it is a problem with scope in the app manifest or perhaps my Node server.
In manifest.json change "display": "fullscreen" to "display": "standalone".
It'll launch your app in App like view.
For more info refer https://developer.mozilla.org/en-US/docs/Web/Manifest

Chrome Ext and JQuery Resources must be listed in the web_accessible_resources manifest key in order to be loaded by pages outside the extension

I have Chrome extension that loads jquery-1.8.3.min.js and jquery-ui.js and jquery-ui-base64.css into the content script .
i use them in the content script NOT background script .
i set the configuration ( i think ) right but when i see in the console i getting errors
i can see the icons in the windows just fine , but i still getting the errors in the Chrome window.
is it a bug in chrome im using version 23.0.1271.95 m?
this is the manifist :
{
"name":"Sample communication from content to background",
"description":"This is a sample for Simulating communication from content to background",
"manifest_version":2,
"version":"2",
"background":{
"scripts":["background.js"]
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["jquery-1.8.3.min.js","jquery-ui.js","client.js"],
"run_at":"document_end",
"all_frames": true,
"css":["jquery-ui-base64.css"]
}
],
"web_accessible_resources": [
"client.js","jquery-1.8.3.min.js","jquery-ui.js","jquery-ui-base64.css",
"images/ui-bg_flat_0_aaaaaa_40x100.png",
"images/ui-bg_flat_75_ffffff_40x100.png",
"images/ui-bg_glass_55_fbf9ee_1x400.png",
"images/ui-bg_glass_65_ffffff_1x400.png",
"images/ui-bg_glass_75_dadada_1x400.png",
"images/ui-bg_glass_75_e6e6e6_1x400.png",
"images/ui-bg_glass_95_fef1ec_1x400.png",
"images/ui-bg_highlight-soft_75_cccccc_1x100.png",
"images/ui-icons_222222_256x240.png",
"images/ui-icons_2e83ff_256x240.png",
"images/ui-icons_454545_256x240.png",
"images/ui-icons_888888_256x240.png",
"images/ui-icons_cd0a0a_256x240.png"
],
"permissions": [
"unlimitedStorage",
"http://*/",
"<all_urls>",
"tabs"
]
}
in the jquery-ui-base64.css i changed all the imags url load to something like this :
url(chrome-extension://__MSG_##extension_id__/chrome-extension://__MSG_##extension_id__/images/ui-bg_flat_75_ffffff_40x100.png)
url(chrome-extension://__MSG_##extension_id__/chrome-extension://__MSG_##extension_id__/images/ui-bg_highlight-soft_75_cccccc_1x100.png)
but still im getting the errors:
Denying load of chrome-extension://mmoccjinakdjcmhjdjghhjnihbfkkgkp/chrome-extension://mmoccjinakdjcmhjdjghhjnihbfkkgkp/images/ui-bg_flat_75_ffffff_40x100.png. Resources must be listed in the web_accessible_resources manifest key in order to be loaded by pages outside the extension.
Denying load of chrome-extension://mmoccjinakdjcmhjdjghhjnihbfkkgkp/chrome-extension://mmoccjinakdjcmhjdjghhjnihbfkkgkp/images/ui-bg_highlight-soft_75_cccccc_1x100.png. Resources must be listed in the web_accessible_resources manifest key in order to be loaded by pages outside the extension.
the images are there in the images dir and i can see the icons in the JQuery dialog i created.
EDIT 1)
The following code works for all background\extension related DOM and css
manifest.json
Simple json structure with all permissions defined
{
"name": "My extension",
"version": "1.0",
"permissions": [
"http://*/*", "tabs", "https://*/*"
],
"browser_action": {
"default_icon": "icon.jpg",
"default_popup":"popup.html"
},
"manifest_version": 2
}
popup.html
Linked style sheet for Browser action Popup
<html>
<head>
<link rel="stylesheet" href="styles.css"></link>
</head>
<body>
</body>
</html>
styles.css
used url() for image path
body{
width : 500px;
height: 500px;
background-image: url('img/icon.jpg');
}
Let me know if it still fails
EDIT 2)
For Injecting Images through content stuff
Solution a)
Using this converter, you convert your image to base64 strings and you can use them as
{ background-image: url(data:image/png;base64,iVBORw ........ };
Solution b)
The following code will not work because
{
background-image:url(chrome.extension.getURL('img/icon.jpg'));
}
chrome.extension.getURL() is undefined for css.
So, i used js for injection of background-images\any image URL's(Because they have dynamic URL's)
manifest.json
Simple json structure with all permissions defined for content scripts and css
{
"name": "My extension",
"version": "1.0",
"permissions": [
"http://*/*", "tabs", "https://*/*"
],
"content_scripts": [
{
"matches": ["<all_urls>"],
"js":["content.js"],
"css": ["styles.css"]
}
],
"web_accessible_resources": [
"img/icon.jpg"
],
"manifest_version": 2
}
content.js
As a trivial use case prepared a div and added background Image property
var newdiv = document.createElement('div');
newdiv.setAttribute("id", "moot450");
document.body.appendChild(newdiv);
document.getElementById('moot450').style.backgroundImage = "url(" + chrome.extension.getURL('img/icon.jpg') + ")";
styles.css
injected another css for refining injected div
#moot450{
position:absolute;
width:500px;
height:500px;
/*background-image:url(chrome-extension://faaligkhohdchiijdkcokpefpancidoo/img/icon.jpg);*/
}
OUTPUT
Screen shot taken from Google Page after injection
Let me know if you need more information or if it fails.

Resources