28

I have 2 Google accounts (personal/work). If I go to the Google Calendar site, on the work account, in Chrome, I can go to ... / More tools / Create shortcut..., which will create a Google Calendar.app file.

I can then add it to the dock - but, this goes to the wrong account :(

If I do Show Package Contents I can open the Info.plist file, and I see the url inside:

    <key>CrAppModeShortcutURL</key>
    <string>https://calendar.google.com/calendar/r</string>

Now if I change the url to https://calendar.google.com/calendar/b/1/r (which is the correct way to choose a specific Google account), I would expect it to work. But in fact that doesn't change anything.

But actually, even if I put any random website here, it will still go to Google Calendar so basically I think this has no effect at all, which is surprising.

Ideas?

0

5 Answers 5

43

I also had this issue (trying to create an app shortcut to the second Google Calendar account) and solved it by creating a web app manifest before creating the app shortcut:

  1. Copy the JavaScript below
  2. On the Google Calendar page, open the Chrome Devtools (right click anywhere on the page and select "Inspect")
  3. Go to the Console tab and paste the copied JavaScript
  4. Modify the URL if necessary to whatever URL you are trying to create an app for
  5. Hit return to run the JavaScript
const startUrl = 'https://calendar.google.com/calendar/u/1/r';

const unsanitizedHtmlPolicy = trustedTypes.createPolicy('unsanitizedHtml', {
  createHTML: (htmlString) => htmlString,
});

document.head
  .querySelector(':first-child')
  .insertAdjacentHTML(
    'beforebegin',
    unsanitizedHtmlPolicy.createHTML(
      `<link rel="manifest" href='data:application/manifest+json,{"start_url":"${startUrl}"}' />`,
    ),
  );

This will add a Web App Manifest for this website which will be used when creating the app.

Once you have done this, you can create the desktop app as normal:

  1. Click on the three dots menu > More Tools > Create Shortcut
  2. Check "Open as window" and select "Create"

Now you should have a separate desktop app for your second calendar account!

Source: https://github.com/karlhorky/pwa-tricks#change-starting-url-of-pwa-in-chrome


Alternatives: Editing Existing Manifest, Security Restriction Workarounds

If you're trying to add a different PWA than Google Calendar where the page either specifies a manifest already or sets the Content Security Policy directive manifest-src or trusted-types, then the above solution may not work, potentially also returning an error such as:

Refused to load manifest from 'data:application/manifest+json,...' because it violates the following Content Security Policy directive: "manifest-src 'self'".

To get around this, two additional options:

A) Manually Edit HTML

Switch to the Elements tab of the Chrome DevTools and manually edit the HTML to match the manifest in the JavaScript above

B) Chrome Local Overrides

Use Chrome Local Overrides to modify the start_url in the Web App Manifest:

  1. On the page of the application, open the Chrome DevTools (right click anywhere on the page and select "Inspect")
  2. Locate and expand the <head> element and find the link element with rel="manifest". Note the file path in href.



  3. Open the Sources tab in the DevTools. If you have not used overrides before, you will need to set them up:
    • Switch to the Overrides 2nd-level tab (you may need to find it in the ยป menu)
    • If you Create a new folder in your projects or Documents folder called chrome-overrides
    • Click on ๏ผ‹ Select folder for overrides and select the folder you created



    • Confirm any prompts at the top of the browser asking for access to the folder



  4. Refresh the page to make sure all sources load. Locate the web app manifest corresponding to the file path you noted earlier. Right click and select Save for overrides:



  5. Now the web app manifest is editable! Make your changes to start_url or anything else that you need, save the file and reload the page
  6. The updated web app manifest has now been loaded, and you can install or create a shortcut to the PWA as normal ๐Ÿ™Œ

Source: https://github.com/karlhorky/pwa-tricks#solution-2-edit-an-existing-web-app-manifest

9
  • Glad that it helped you!
    – Karl Horky
    Commented Aug 28, 2020 at 17:00
  • 2
    This worked for YouTube not letting me set the URL for a video I wanted a focus video soundtrack (e.g. youtube.com/watch?v=_JJJBQSBiA4&t=83s) that I wanted PWAize to a specific timestamp. It was always stripping the entire path off, to www.youtube.com. The first method worked and took about 30 seconds once I was done reading, which for some reason looked more complicated than it actually was. I did have to uninstall the previous PWA/Shortcut and re-add it, as expected. I suppose this solution could be packaged as a Chrome Extension too. Commented Apr 8, 2022 at 0:18
  • 1
    Thank you so much for the detailed guide! The first option didn't work for me (trying to save Google Meet with a secondary account as a desktop shortcut), but the second (via overrides) did! Just a note, though, is that I got an error on the first option command: VM182:4 This document requires 'TrustedHTML' assignment. VM182:4 Uncaught TypeError: Failed to execute 'insertAdjacentHTML' on 'Element': This document requires 'TrustedHTML' assignment. - Maybe be you can double check this and update your repo with updated instructions? Cheers! Commented Jan 27, 2023 at 13:01
  • 1
    This solution works perfectly for Notion on Windows as well. Thanks!
    – Winand
    Commented Feb 11, 2023 at 11:06
  • 1
    Also, btw, you can use your Google account's email address in place of the number in the /u/1 part of Google urls (e.g., /calendar/u/[email protected]/r). Google will redirect to the correct /u/N for that email. (The numbers can change depending on the order you log into your Google accounts.)
    – medmunds
    Commented Sep 13, 2023 at 19:45
3

Introduction

I have used the JavaScript from this answer by Karl, but noticed the installed app would use a crude favicon, instead of the nicer icons defined in the original manifest.

I have adapted the script to update the original manifest, instead of creating a new one.

11.01.2024 edit based on suggestion and answer by Jason: Keep start url search parameters, account for location hash, set navigation scope and avoid breaking absolute icon paths.

Customization

To change the start URL and other details, we can make the browser use our own customized manifest:

  1. Open the website and go to the desired URL. If not possible, adjust url in the script.
  2. Open the console tab in developer tools, in Chrome or Edge: Ctrl + Shift + I.
  3. Copy the JavaScript below into the console and press Enter.
  4. Close the console and add an application, Menu > Apps > Install this site as an app.

Customizing the original manifest keeps the icons and other details we do not want to change.

The script

manifest = document.head.querySelector('link[rel="manifest"]');
json = await fetch(manifest.href).then(res => res.json());

url = new URL(window.location);
url.search = new URLSearchParams([...new URL(json.start_url, url).searchParams, ...url.searchParams]);

json.start_url = url.href;
json.scope = url.origin + url.pathname.substring(0, url.pathname.lastIndexOf('/') + 1);
json.icons.forEach((icon) => icon.src = new URL(icon.src, manifest.href).href);

manifest.href = 'data:application/manifest+json,' + encodeURIComponent(JSON.stringify(json));

json;

With comments

// Find the head node linking to the original manifest.
manifest = document.head.querySelector('link[rel="manifest"]');

// Fetch the manifest and convert it to json.
json = await fetch(manifest.href).then(res => res.json());

// Create a new url for the current location, keeping the parameters from the original start url.
url = new URL(window.location);
url.search = new URLSearchParams([...new URL(json.start_url, url).searchParams, ...url.searchParams]);

// Set the start url and scope. Will revert to a normal browser when navigated outside of scope.
json.start_url = url.href;
json.scope = url.origin + url.pathname.substring(0, url.pathname.lastIndexOf('/') + 1);

// Convert relative icon paths in the manifest into absolute paths.
// When the manifest is defined inline, the paths must be absolute and include protocol.
json.icons.forEach((icon) => icon.src = new URL(icon.src, manifest.href).href);

// Store the updated manifest inline in the original link node.
// Must be URI-encoded, otherwise the # in the colors would cause parsing errors.
manifest.href = 'data:application/manifest+json,' + encodeURIComponent(JSON.stringify(json));

// Dump the updated manifest into the console for review.
json;
2
  • This answer is awesome! Thank you. One correction: All the icons in the manifest are absolute paths (atleast for Gmail, Calendar and Chat). So just remove this line from the above. json.icons.forEach((icon) => icon.src = base + icon.src); The above actually corrupts the path and the icons don't work. Commented Jan 5 at 20:01
  • Thanks! The code was based on Google Keep which uses relative paths for icons. These are rejected if manifest is defined inline. I have now edited my answer to avoid breaking already absolute paths and added a few other improvements suggested by Jason. Seems to work fine with Keep, Gmail and Calendar.
    – Pawel
    Commented Jan 11 at 14:15
1

I tried making a desktop app shortcut for Microsoft 365 outlook calendar and it always redirected back to the mail component. This solution to this question didn't work for me but going into the sources tab and manually editing the url manifest file and then creating the shortcut worked like a charm. Just putting it on here incase the other solution doesn't work, mine might.

2
  • Right, I actually also just added detailed instructions about this to my pwa-tricks repo 3 days ago ๐Ÿ˜„ : github.com/karlhorky/…
    – Karl Horky
    Commented Jan 16, 2021 at 11:03
  • I will also add a section to the answer above referencing this second solution in the repo - should have also updated it 3 days ago.
    – Karl Horky
    Commented Jan 16, 2021 at 11:03
1

The first part of Karl Horky's answer above is correct, however, it seems that Google has since started using TrustedHTML on their web apps so it no longer works for Gmail, Google Calendar, etc.

This is an update to his answer that works on Google and non-Google sites.

const startUrl = 'https://calendar.google.com/calendar/u/1/r';

const escapeHTMLPolicy = trustedTypes.createPolicy("forceInner", { createHTML: (to_escape) => to_escape });
document.head
  .querySelector(':first-child')
  .insertAdjacentHTML(
    'beforebegin',
    escapeHTMLPolicy.createHTML(`<link rel="manifest" href='data:application/manifest+json,{"start_url":"${startUrl}"}' />`),
  );
1
  • Thanks Andrew! ๐Ÿ™Œ I've also edited my answer and my repo to avoid misleading people
    – Karl Horky
    Commented Jul 15, 2023 at 15:21
1

Building on Paul's comment (with agarza's edit), this adds the ability to keep the existing query params and hash (e.g. #inbox), and only changes icons with relative paths.

// Find the head node linking to the original manifest.
manifest = document.head.querySelector('link[rel="manifest"]');

// Fetch the manifest and convert it to json.
json = await fetch(manifest.href).then(res => res.json());

// Create URL objects for current location and manifest start_url with current location as URL base, then combine query parameters.
window_href = new URL(window.location);
start_href = new URL(json.start_url, window_href);
window_href.search = new URLSearchParams({
  ...Object.fromEntries(window_href.searchParams),
  ...Object.fromEntries(start_href.searchParams)
});

// Set the start url to the current location with combined query parameters.
json.start_url = window_href.href;
json.scope = window_href.pathname;

// Convert relative icon paths in the manifest into absolute paths.
json.icons.forEach((icon) => icon.src = (icon.src.startsWith('http') || icon.src.startsWith('//')) ? icon.src : base + icon.src)

// Store the updated manifest in-place in the original link node.
// Must be URI-encoded.
manifest.href = `data:application/manifest+json,${encodeURIComponent(JSON.stringify(json))}`;

json;

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .