Preventing Angular from running when accessing static files with no extension
Asked Answered
G

2

6

I have an Ionic/Angular PWA for a project, which has a native (Ionic Cordova) app as well for mobile. On the app we need to accept deeplinks that will have the same URL as the PWA (so you can access from PC and from mobile using the same link).

For iOS deeplinks ("universal links" as they call them) to work, the TLD needs to provide some file called apple-app-site-association (without extension), at the root folder or inside the .well-known path. Same goes for Android, but with a file called assetlinks.json (although in this case it is not really necessary for this to work, it is just there to comply with some Android guidelines).

The thing is, when I try to access the iOS file, my PWA opens up and catches it as a route and redirects to the home page. With the Android file it works well though, since it has an extension and Angular (or the hosting server) recognizes that the browser wants to access some static file.

I have looked this up throughout the internet and could not find any solution...

The website is hosted on an IIS server, and I have tried some settings fixes on the web.config file such as rewriting the path to an identical file with a .json extension and another one I do not seem to remember right now.

I did try all the combinations I could think of for the routes in the src/app-routing.module.ts file as well. If there is a wildcard route it will be matched and redirected to wherever is specified. If not, I get an Error: Cannot match any routes. URL Segment: '.well-known/apple-app-site-association'.

Glutton answered 17/7, 2020 at 21:12 Comment(1)
hey @user8738866, any updates on this one? did you manage to solve it?Metaphysical
H
0

If you read about universal links you will learn that the concept is: the hosting site for your app serves apple-app-site-association to connect the url to your iOS app. Setting this up for static websites is straightforward. But for Spa apps this can be quite difficult. For example, with Angular, it is easy to configure static content via angular.json. But, when that static content does not have a file extension, it no longer works 🤔 (gets forwarded to Angular routing, which is not what you want).

So, in the case of an Spa, what you really need to do is setup some additional infrastructure in front of your app. This additional universal-link infrastructure is just a minimal website accessed using a different url from your app-url.

The universal-link url is what you promote to your user base. When the universal-link site is accessed, if the user is on a mobile device, the apple-app-site-associations file is returned to the device and used to open the associated native app. If the user is not on a mobile device, then the web-app that is configured for the universal link is opened.

I am including a simple illustration of this here. This explanation is not intended to be exhaustive, rather to point you (who have become stuck) in the right direction. The use-case in the diagram is for the Firebase implementation of universal links -- Dynamic links

enter image description here

Some good resources for universal links:

Hezekiah answered 7/5, 2022 at 13:56 Comment(0)
S
0

How to use Angular to serve a static file without a file extension

Use case: Apple sometimes verifies domains by requesting apps to host a file at a URL such as:

https://example.com/.well-known/apple-developer-merchantid-domain-association

Note 1

If Apple allowed serving the file from the /assets directory, then it could be placed in /assets and accessed via https://example.com/assets/apple-developer-merchantid-domain-association

Note 2

Or if Apple added a file extension like .txt, then you could update angular.json with:

  "assets": [
    { "glob": "**/*", "input": "my-app/src/.well-known/", "output": "/.well-known/" },
  ],

and then access the file with:

https://example.com/.well-known/apple-developer-merchantid-domain-association.txt

Solution

When you cannot serve the file from /assets and you cannot add a file extension, then a workaround is to create a custom URL Matcher and then use the HTTP Client to load the file as a blob.

Step 1: Create custom UrlMatch

import { UrlMatcher, UrlSegment } from '@angular/router'

export const AppleVerificationUrlMatcher: UrlMatcher = (segments: UrlSegment[]) => {
  if (
    segments.length === 2 &&
    segments[0]?.path === '.well-known' &&
    segments[1]?.path === 'apple-developer-merchantid-domain-association'
  ) {
    return { consumed: segments }
  }
  return null
}

Step 2: Create a component to load the file as a blob

import { Component } from '@angular/core'
import { HttpClient } from '@angular/common/http'
import { ActivatedRoute, Router } from '@angular/router'
import { map, tap } from 'rxjs/operators'

@Component({
  selector: 'app-apple-verification-handler',
  template: '',
  standalone: true,
})
export class AppleVerificationComponent {
  constructor(private http: HttpClient, private route: ActivatedRoute, private router: Router) {
    const url = this.router.url.slice(1) // Remove leading slash
    this.http
      .get(`${url}`, { responseType: 'text' })
      .pipe(
        tap((content) => {
          const contentType = 'text/plain'
          const blob = new Blob([content], { type: contentType })
          const url = URL.createObjectURL(blob)
          window.location.href = url
        }),
        map(() => null)
      )
      .subscribe()
  }
}

Step 3: Update your Angular routes

import { Route } from '@angular/router'
import { AppleVerificationComponent } from './apple-verification.component'
import { AppleVerificationUrlMatcher } from './apple-verification.url-matcher'

export const appRoutes: Route[] = [
  {
    matcher: AppleVerificationUrlMatcher,
    component: AppleVerificationComponent,
  },
  ...
Stere answered 19/6 at 18:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.