3

I have a project which needs to create a dynamic manifest.json file for my PWA (ReactJS).

Please see below my codes:

app.service.js:

        function getAppDetails() {
            const requestOptions = {
                method: 'GET',
                headers: authHeader()
            };
            // TODO create dynamic manifest.json file here
            return fetch(`${config.apiUrl}/api/apps`, requestOptions).then(handleResponse);
        }   

        function handleResponse(response) {
            return response.text().then(text => {
                const data = text && JSON.parse(text);
                if (!response.ok) {
                    if (response.status === 401) {
                        // auto logout if 401 response returned from api
                        logout();
                        location.reload(true);
                    } 
                    const error = (data && data.message) || response.statusText;
                    return Promise.reject(error);
                }

                return data;
            });
}

app.actions.js:

function getAppDetails() {
        return dispatch => {
            dispatch(request());

            appService.getAppDetails()
                .then(
                    details => dispatch(success(details)),
                    error => dispatch(failure(error.toString()))
                );
        };

        function request() { return { type: appConstants.GETDETAILS_REQUEST } }
        function success(details) { return { type: appConstants.GETDETAILS_SUCCESS, details } }
        function failure(error) { return { type: appConstants.GETDETAILS_FAILURE, error } }
}

LoginPage.jsx:

    import { appActions } from '../_actions';
    class LoginPage extends React.Component {
        constructor(props) {
            super(props);

            // reset login status
            this.props.dispatch(userActions.logout());

            this.state = {
                email: '',
                password: '',
                isDisabled: false,
                submitted: false
            };

            this.handleChange = this.handleChange.bind(this);
            this.handleSubmit = this.handleSubmit.bind(this);
            this.showSwal = this.showSwal.bind(this);
        }

        componentDidMount() {
            // TODO call the action to add dynamic manifest file to index.html
            this.props.dispatch(aapActions.getAppDetails());
        }

        render() {
            return (
                ................
            );
        }
}

I am new to this kind of thing. How can I get started?

Sanjeet kumar
  • 3,333
  • 3
  • 17
  • 26
Erlinda Santiago
  • 31
  • 1
  • 1
  • 2
  • 1
    It may be hard for people to help here, since they may not be sure where you are stuck. Did you try the task you describe, and had some sort of difficulty? If you can narrow the issue down, that might help people help you. – halfer Oct 30 '18 at 20:50

5 Answers5

12

if you want to create dynamic manifest.json you want to have a link tag in the HTML with rel="manifest" but without href attribute. And use this tag later to populate your manifest. As in

<!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8">
        <title>title</title>
        <link rel="manifest" id="my-manifest-placeholder">
      </head>
      <body>
        <!-- page content -->
      </body>
    </html>

Use a JSON object to set your manifest

var myDynamicManifest = {
  "name": "Your Great Site",
  "short_name": "Site",
  "description": "Something dynamic",
  "start_url": "<your-url>",
  "background_color": "#000000",
  "theme_color": "#0f4a73",
  "icons": [{
    "src": "whatever.png",
    "sizes": "256x256",
    "type": "image/png"
  }]
}
const stringManifest = JSON.stringify(myDynamicManifest);
const blob = new Blob([stringManifest], {type: 'application/json'});
const manifestURL = URL.createObjectURL(blob);
document.querySelector('#my-manifest-placeholder').setAttribute('href', manifestURL);
  • Unfortunately, this solution fails to work correctly in some cases - [here](https://stackoverflow.com/a/71246165/2962507)'s an explanation why. – dan Feb 24 '22 at 01:57
  • To me it seems that this solution doesn't work with Firefox for Android. It doesn't offer the "install" menu item if href value was dynamically set up. However, if the app is already installed through Chrome, it would show the "open in app" option. – viam0Zah Oct 17 '22 at 05:53
  • Yes same for me. – Bhavin Thummar Dec 20 '22 at 14:10
  • Is any other solution which are working ? – Bhavin Thummar Dec 20 '22 at 14:11
  • @viam0Zah see the FirefoxAndroid issue linked in my answer https://stackoverflow.com/a/76882058/945231 – Călin Darie Aug 11 '23 at 09:04
5

Inspired from @Sanjeet kumar's solution for my NextJs application:

// _document.tsx
<link href="/manifest.json" rel="manifest" id="manifest" />


// _app.tsx
import manifest from "../../public/manifest.json";

const manifestElement = document.getElementById("manifest");
const manifestString = JSON.stringify({
  ...manifest,
  start_url: `${homePagePath}${storeCode}`,
});
manifestElement?.setAttribute(
  "href",
  "data:application/json;charset=utf-8," + encodeURIComponent(manifestString)
);
  • 4
    using this approach gives me `Manifest: property 'start_url' ignored, URL is invalid.` – SushiWaUmai Oct 16 '21 at 18:46
  • @SushiWaUmai this is likely due to how you have `start_url` defined - it's supposed to be set as explained [here](https://developer.mozilla.org/en-US/docs/Web/Manifest/start_url) - for example, `/`, `.` or `https://example.org/path`. You can use the [Lighthouse PWA analysis tool](https://developers.google.com/web/ilt/pwa/lighthouse-pwa-analysis-tool) to verify it's set correctly. – dan Feb 24 '22 at 02:18
  • Trying to set it inside a component with `useEffect` didn't seem to work, is it possible to change it dynamically depending upon the query params? – Tayyab Ferozi Nov 26 '22 at 18:03
  • @SushiWaUmai, see the caveat about relative URLs covered in my answer https://stackoverflow.com/a/76882058/945231 – Călin Darie Aug 11 '23 at 09:04
4

I was facing some issues with the Blob, It was not working in android as expected. The below answer works fine.

 var myDynamicManifest = {
  "name": "Your Great Site",
  "short_name": "Site",
  "description": "Something dynamic",
  "start_url": "<your-url>",
  "background_color": "#000000",
  "theme_color": "#0f4a73",
  "icons": [
      {
        src: "icon_size_36.png",
        sizes: "36x36",
        type: "image/png",
      },
      {
        src: "icon_size_48.png",
        sizes: "48x48",
        type: "image/png",
      },
      {
        src: "icon_size_72.png",
        sizes: "72x72",
        type: "image/png",
      },
      {
        src: "icon_size_96.png",
        sizes: "96x96",
        type: "image/png",
      },
      {
        src: "icon_size_144.png",
        sizes: "144x144",
        type: "image/png",
      },
      {
        src: "icon_size_192.png",
        sizes: "192x192",
        type: "image/png",
      },
      {
        src: "icon_size_512.png",
        sizes: "512x512",
        type: "image/png",
      },
    ]
  }
  const link = document.createElement("link");
  link.rel = "manifest";    
  const stringManifest = JSON.stringify(myDynamicManifest);
  link.setAttribute('href', 'data:application/json;charset=utf-8,' + encodeURIComponent(stringManifest))
  document.head.appendChild(link);
Sanjeet kumar
  • 3,333
  • 3
  • 17
  • 26
  • I believe there's still an issue even with this approach on Android - if you ever update manifest.json, it may fail to apply the update. Please see my comment [here](https://stackoverflow.com/a/71246165/2962507) on it. – dan Feb 24 '22 at 01:58
3

Unfortunately, the advice on using blob: or data: URLs fails to work correctly in some cases, because manifest.json URLs are supposed to be static:

Do not change the name or location of your web app manifest file, doing so may prevent the browser from updating your PWA.

As that article explains, there's a special mechanism for updates to manifest.json (triggered by very specific conditions) - it is not supposed to be updated via the URL name.

Moreover, Android may no longer recognize the app as a full (WebAPK-installable) PWA, even when the app passes the Lighthouse check. In that case, it's still "installed" as a regular PWA with an ugly Chrome icon in the corner of the app launch icon. I've spend many hours trying to understand why, and finally stumbled on that article with the explanation.

So please either

  1. Serve dynamic manifest.json from a completely static URL, or
  2. Determine href based on some attribute (e.g. hostname) that doesn’t change over the lifetime of the app (so that different variations of manifest could be served for different clients, but each client’s version would have a static URL).
dan
  • 1,144
  • 12
  • 17
  • What do you mean? The solution you link above actually IS changing the `href` property with diffrent `data:` payload... – Shockwaver Mar 18 '22 at 09:05
  • Thanks for the note, I meant it more from the perspective of fetching manifest from a URL dynamically. I’ve updated the answer to make it more clear. – dan Mar 19 '22 at 12:17
0

Starting with a <link rel="manifest"> and setting a href from javascript works, with both data: and blob: URLs.

Known issues:

  • relative paths for start_url, scope and icons won't work. They'll be interpreted as relative to the blob: or data: urls, which is invalid. Symptom: Manifest: property 'start_url' ignored. Solution: build absolute URL, e.g. for URLs with the same origin as the frontend, I've used
    new URL(`/my/absolute/path/?evensomeparameter=${valueVariable}`,
   window.location.origin).href
  • Content-Security-Policy may prevent the manifest from being loaded from blob: or data:. Symptom: in the console, you'll find errors stating refused to load manifest from 'blob:https://my.domain.com/blah' because it violates the following Content Security Policy directive:. Solution: you can enable manifest-src blob: or manifest-src data: in the Content Security Policy. This comes either from a response header - see load balancer / CDN / server config - , or from a <meta head directive on your page https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy
  • there's an issue last updated in 2022 stating that Firefox on Android only read the manifest on document load, so setting the href with a delay won't work https://github.com/mozilla-mobile/fenix/issues/16672

Just for completeness sake, an alternative: take advantage of session data stored in cookies and generate the manifest server-side. But take care of allowed domains - see Content-Security-Policy above. And figure out where you want the static assets like icons and splash screen to be served from. Prefer absolute URLs.

https://medium.com/limehome-engineering/create-a-pwa-app-manifest-dynamically-spa-angular-1627260e0390

Călin Darie
  • 5,937
  • 1
  • 18
  • 13