From db9f67589ce6372abdbcc1c87d3b4d0905d98ef4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Denis=20=C5=A0vara?= Date: Tue, 24 Feb 2026 11:25:07 +0100 Subject: [PATCH 1/4] Implement `ClientConfigurationsController` for mobile client path config. --- .../client_configurations_controller.rb | 15 ++++++ .../client_configurations_controller_test.rb | 47 +++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 app/controllers/client_configurations_controller.rb create mode 100644 test/controllers/client_configurations_controller_test.rb diff --git a/app/controllers/client_configurations_controller.rb b/app/controllers/client_configurations_controller.rb new file mode 100644 index 000000000..0b0f2f670 --- /dev/null +++ b/app/controllers/client_configurations_controller.rb @@ -0,0 +1,15 @@ +class ClientConfigurationsController < ApplicationController + skip_before_action :require_account, :require_authentication + allow_unauthorized_access + + def show + expires_in 1.minute, public: true + + render action: client_configuration_name + end + + private + def client_configuration_name + "#{params.require(:platform)}_v#{params.require(:version)}" + end +end diff --git a/test/controllers/client_configurations_controller_test.rb b/test/controllers/client_configurations_controller_test.rb new file mode 100644 index 000000000..b2aa00e6d --- /dev/null +++ b/test/controllers/client_configurations_controller_test.rb @@ -0,0 +1,47 @@ +require "test_helper" + +class ClientConfigurationsControllerTest < ActionDispatch::IntegrationTest + test "android" do + assert_ok "/client_configurations/android_v1.json" + end + + test "ios" do + assert_ok "/client_configurations/ios_v1.json" + end + + test "bad platform" do + assert_no_route "/client_configurations/blackberry_v1.json" + end + + test "bad version" do + assert_no_route "/client_configurations/android_va.json" + end + + test "nonexistent version" do + assert_missing "/client_configurations/android_v2000.json" + assert_missing "/client_configurations/ios_v2000.json" + end + + private + def assert_ok(url, cache_control: { public: true, max_age: "60" }) + get url + assert_response :ok + + assert_kind_of Hash, response.parsed_body["settings"] + assert_kind_of Array, response.parsed_body["rules"] + + assert_equal cache_control, response.cache_control + end + + def assert_no_route(url) + without_action_dispatch_exception_handling do + assert_raises(ActionController::RoutingError) { get url } + end + end + + def assert_missing(url) + without_action_dispatch_exception_handling do + assert_raises(ActionView::MissingTemplate) { get url } + end + end +end From de3c8c46b5e825020f458f0bd378848b217caeaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Denis=20=C5=A0vara?= Date: Tue, 24 Feb 2026 11:25:43 +0100 Subject: [PATCH 2/4] Add constrained route. --- config/routes.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/config/routes.rb b/config/routes.rb index 979d2a86b..0ca383dda 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -245,6 +245,10 @@ Rails.application.routes.draw do get "manifest" => "rails/pwa#manifest", as: :pwa_manifest get "service-worker" => "pwa#service_worker" + # Mobile clients + get "client_configurations/(:platform)_v(:version)" => "client_configurations#show", + platform: /android|ios/, version: /\d+/ + namespace :admin do mount MissionControl::Jobs::Engine, at: "/jobs" end From e00a51b6c234fdbd7156261a903ed66a815a4fa7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Denis=20=C5=A0vara?= Date: Tue, 24 Feb 2026 11:26:00 +0100 Subject: [PATCH 3/4] Add android path config. --- .../client_configurations/android_v1.json | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 app/views/client_configurations/android_v1.json diff --git a/app/views/client_configurations/android_v1.json b/app/views/client_configurations/android_v1.json new file mode 100644 index 000000000..706d54b6f --- /dev/null +++ b/app/views/client_configurations/android_v1.json @@ -0,0 +1,90 @@ +{ + "settings": {}, + "rules": [ + { + "patterns": [ + ".*" + ], + "properties": { + "context": "default", + "presentation": "default", + "query_string_presentation": "replace", + "uri": "hotwire://fragment/web", + "fallback_uri": "hotwire://fragment/web", + "pull_to_refresh_enabled": true + } + }, + { + "patterns": [ + "/new$", + "/new\\?.+$", + "/edit$", + "/edit\\?.+$", + "/cards/[0-9]+/draft", + "/notifications/settings", + "/account/settings", + "/account/join_code" + ], + "properties": { + "context": "modal", + "pull_to_refresh_enabled": false + } + }, + { + "patterns": [ + "/native/login/blank" + ], + "properties": { + "uri": "hotwire://fragment/login/blank" + } + }, + { + "patterns": [ + "/native/login/email" + ], + "properties": { + "uri": "hotwire://fragment/login/email" + } + }, + { + "patterns": [ + "/native/login/magic_code" + ], + "properties": { + "uri": "hotwire://fragment/login/magic_code" + } + }, + { + "patterns": [ + "/native/login/signup_completion" + ], + "properties": { + "uri": "hotwire://fragment/login/signup_completion" + } + }, + { + "patterns": [ + "/native/settings" + ], + "properties": { + "uri": "hotwire://fragment/settings" + } + }, + { + "patterns": [ + "/my/pins" + ], + "properties": { + "uri": "hotwire://fragment/pins" + } + }, + { + "patterns": [ + "/notifications$" + ], + "properties": { + "uri": "hotwire://fragment/notifications" + } + } + ] +} \ No newline at end of file From c0c78152c9a27e031feda4ccdbfdb3fbc5487f5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Denis=20=C5=A0vara?= Date: Tue, 24 Feb 2026 11:26:07 +0100 Subject: [PATCH 4/4] Add ios path config. --- app/views/client_configurations/ios_v1.json | 89 +++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 app/views/client_configurations/ios_v1.json diff --git a/app/views/client_configurations/ios_v1.json b/app/views/client_configurations/ios_v1.json new file mode 100644 index 000000000..2285e9bc7 --- /dev/null +++ b/app/views/client_configurations/ios_v1.json @@ -0,0 +1,89 @@ +{ + "settings": { + }, + "rules": [ + { + "patterns": [ + ".*" + ], + "properties": { + "context": "default", + "query_string_presentation": "replace", + "pull_to_refresh_enabled": true + } + }, + { + "patterns": [ + "/new$", + "/new\\?.+$", + "/edit$", + "/edit\\?.+$", + "/accounts$", + "/cards/[0-9]+/draft" + ], + "properties": { + "context": "modal", + "pull_to_refresh_enabled": false + } + }, + { + "patterns": [ + "/native/my_menu$" + ], + "properties": { + "context": "modal", + "view_controller": "main_menu" + } + }, + { + "patterns": [ + "/native/add_account$" + ], + "properties": { + "context": "modal", + "view_controller": "login" + } + }, + { + "patterns": [ + "/native/login$" + ], + "properties": { + "view_controller": "login" + } + }, + { + "patterns": [ + "/my/pins$" + ], + "properties": { + "view_controller": "pinned" + } + }, + { + "patterns": [ + "/notifications/tray$" + ], + "properties": { + "view_controller": "notifications" + } + }, + { + "patterns": [ + "internal/devtools/enable*" + ], + "properties": { + "context": "modal", + "view_controller": "dev_settings_launcher" + } + }, + { + "patterns": [ + "/native/settings$" + ], + "properties": { + "view_controller": "settings" + } + } + ] +}