FEATURE: Site settings for linking with iOS/Android native apps

FEATURE: Site settings for linking with iOS/Android native apps

  • Adds support for iOS Universal Links via an apple-app-site-association endpoint

Adds support for Google Digital Asset Links at the .well-known/assetlinks.json endpoint

diff --git a/app/controllers/metadata_controller.rb b/app/controllers/metadata_controller.rb
index 8148b9c..189852a 100644
--- a/app/controllers/metadata_controller.rb
+++ b/app/controllers/metadata_controller.rb
@@ -12,6 +12,16 @@ class MetadataController < ApplicationController
     render template: "metadata/opensearch.xml"
   end
 
+  def app_association_android
+    raise Discourse::NotFound unless SiteSetting.app_association_android.present?
+    render plain: SiteSetting.app_association_android, content_type: 'application/json'
+  end
+
+  def app_association_ios
+    raise Discourse::NotFound unless SiteSetting.app_association_ios.present?
+    render plain: SiteSetting.app_association_ios, content_type: 'application/json'
+  end
+
   private
 
   def default_manifest
diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml
index 9a86c6a..a195438 100644
--- a/config/locales/server.en.yml
+++ b/config/locales/server.en.yml
@@ -1929,6 +1929,9 @@ en:
 
     native_app_install_banner_android: "Displays DiscourseHub app banner on Android devices to regular users (trust level 1 and up)."
 
+    app_association_android: "Contents of <a href='/.well-known/assetlinks.json'>.well-known/assetlinks.json</a> endpoint, used for Google's Digital Asset Links API."
+    app_association_ios: "Contents of <a href='/apple-app-site-association'>apple-app-site-association</a> endpoint, used to create Universal Links between this site and iOS apps."
+
     share_anonymized_statistics: "Share anonymized usage statistics."
 
     auto_handle_queued_age: "Automatically handle records that are waiting to be reviewed after this many days. Flags will be ignored. Queued posts and users will be rejected. Set to 0 to disable this feature."
diff --git a/config/routes.rb b/config/routes.rb
index befb4d1..7b63826 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -812,6 +812,8 @@ Discourse::Application.routes.draw do
   get "offline.html" => "offline#index"
   get "manifest.webmanifest" => "metadata#manifest", as: :manifest
   get "manifest.json" => "metadata#manifest"
+  get ".well-known/assetlinks.json" => "metadata#app_association_android"
+  get "apple-app-site-association" => "metadata#app_association_ios", format: false
   get "opensearch" => "metadata#opensearch", constraints: { format: :xml }
 
   scope "/tags" do
diff --git a/config/site_settings.yml b/config/site_settings.yml
index bd25b6f..99c53f3 100644
--- a/config/site_settings.yml
+++ b/config/site_settings.yml
@@ -1931,6 +1931,16 @@ uncategorized:
     default: "iPad|iPhone"
     hidden: true
 
+  app_association_android:
+    client: false
+    default: ""
+    textarea: true
+
+  app_association_ios:
+    client: false
+    default: ""
+    textarea: true
+
   share_anonymized_statistics: true
 
   auto_handle_queued_age:
diff --git a/spec/requests/metadata_controller_spec.rb b/spec/requests/metadata_controller_spec.rb
index 473d65d..eaa8f05 100644
--- a/spec/requests/metadata_controller_spec.rb
+++ b/spec/requests/metadata_controller_spec.rb
@@ -101,4 +101,53 @@ RSpec.describe MetadataController do
       expect(response.content_type).to eq('application/xml')
     end
   end
+
+  describe '#app_association_android' do
+    it 'returns 404 by default' do
+      get "/.well-known/assetlinks.json"
+      expect(response.status).to eq(404)
+    end
+
+    it 'returns the right output' do
+      SiteSetting.app_association_android = <<~EOF
+        [{
+          "relation": ["delegate_permission/common.handle_all_urls"],
+          "target" : { "namespace": "android_app", "package_name": "com.example.app",
+                       "sha256_cert_fingerprints": ["hash_of_app_certificate"] }
+        }]
+      EOF
+      get "/.well-known/assetlinks.json"
+
+      expect(response.status).to eq(200)
+      expect(response.body).to include("hash_of_app_certificate")
+      expect(response.body).to include("com.example.app")
+      expect(response.content_type).to eq('application/json')
+    end
+  end
+
+  describe '#app_association_ios' do
+    it 'returns 404 by default' do
+      get "/apple-app-site-association"
+      expect(response.status).to eq(404)
+    end
+
+    it 'returns the right output' do
+      SiteSetting.app_association_ios = <<~EOF
+        {
+          "applinks": {
+            "apps": []
+          }
+        }
+      EOF
+      get "/apple-app-site-association"
+
+      expect(response.status).to eq(200)
+      expect(response.body).to include("applinks")
+      expect(response.content_type).to eq('application/json')
+
+      get "/apple-app-site-association.json"
+      expect(response.status).to eq(404)
+    end
+  end
+
 end

GitHub sha: 5c02bfb0

1 Like