Universal Links на iOS и App Links на Android оба требуют JSON-файл, отдаваемый по HTTPS из корня вашего домена. iOS хочет /.well-known/apple-app-site-association. Android хочет /.well-known/assetlinks.json. localhost не может отдать ни один по имени хоста, которое ОС свяжет с вашим приложением. Поэтому туннелируем.
Реальное требование на каждой платформе
iOS Universal Links
ОС загружает https://ваш-домен.com/.well-known/apple-app-site-association (или /apple-app-site-association) при установке приложения. Сверяет содержимое с entitlement associated-domains вашего приложения. Загрузка должна пройти по настоящему HTTPS. iOS не доверяет для этого самоподписанным сертификатам и не доверяет localhost.
Android App Links
Android верифицирует App Links, загружая https://ваш-домен.com/.well-known/assetlinks.json. Файл должен перечислять SHA-256-отпечатки вашего приложения. Верификация происходит при установке и при первом запуске. То же ограничение: настоящий HTTPS, настоящий домен.
Почему localhost не может это напрямую
Даже когда mkcert даёт доверенный HTTPS в браузере ноутбука, мобильная ОС не доверяет вашему локальному CA. И домен в apple-app-site-association должен совпадать с associated-domain в entitlement приложения — это настоящий, разрешаемый через DNS домен, а не localhost.
Некоторые команды обходят это выделенным staging-доменом. Это работает, но цикл итераций медленный. localhost-туннель даёт настоящий HTTPS-URL на настоящем поддомене, до которого мобильные устройства достучатся без претензий.
Как это подключить
- Отдавайте
apple-app-site-associationиassetlinks.jsonс локального dev-сервера по пути/.well-known/. Оба файла. Оба маршрута. - Запустите
npx portpreview 3000(или тот порт, что использует ваш dev-сервер). - Запишите имя хоста туннеля — что-то вроде
abc123.portpreview.dev. - В entitlement associated-domains вашего iOS-приложения добавьте
applinks:abc123.portpreview.dev. В intent-filter Android — тот же хост. - Соберите и установите приложение на реальное устройство (у симуляторов странное поведение Universal Link).
- Откройте URL из другого приложения — Заметки, Почта, QR-код — и наблюдайте, как он направляется в установленное приложение, а не в браузер.
Подвох туннелирования для диплинков: имя хоста туннеля меняется между сессиями, если нет зарезервированного поддомена. Каждая ротация означает пересборку приложения с новой записью associated-domain. Если вы часто итерируете поведение диплинков, заведите зарезервированный поддомен.
Content-type важен для apple-app-site-association
iOS ожидает файл без расширения и либо content-type application/json (новее iOS), либо application/pkcs7-mime (старый, подписанный формат). Почти все современные приложения используют простой JSON-вариант. Убедитесь, что dev-сервер возвращает правильный content-type, иначе iOS молча отклонит файл без полезной ошибки.
Сначала протестируйте из десктоп-браузера: откройте https://ваш-туннель.portpreview.dev/.well-known/apple-app-site-association и убедитесь, что JSON рендерится, а заголовок content-type в dev tools верный. Если не так, почините до охоты на баги Universal Link в симуляторе.
Другие ловушки отладки диплинков
Несовпадение entitlement приложения
Если ваш entitlement associated-domains говорит applinks:abc.portpreview.dev, а apple-app-site-association перечисляет def.portpreview.dev, iOS не загружает. Имя хоста должно быть согласовано.
Universal Link изнутри Safari
Тап по Universal Link внутри Safari (того же приложения, где открыта вкладка) иногда открывает в Safari, а не в приложении. Это сделано намеренно — iOS предотвращает трюк «открыть приложение всякий раз, когда пользователь нажимает ссылку». Тестируйте из Заметок или Почты.
Android App Links и digital asset links
Android также поддерживает кастомные URL-схемы (вашеприложение://путь), которым не нужен HTTPS или assetlinks.json. Их проще тестировать, но они менее безопасны — любое приложение может зарегистрировать ту же схему. Для диплинкинга продакшен-качества ответ — App Links.
Наш процесс мобильного тестирования
Для проекта, выпускающего и iOS, и Android с диплинками:
- Запустите бэкенд, отдающий файлы
.well-known. - Туннелируйте его через
npx portpreview 3000. - Как только URL туннеля стабилен для сессии (или используйте зарезервированный поддомен), обновите записи associated-domain приложения и соберите.
- QA на физических устройствах — и свежие установки, и обновления.
- Для комбо OAuth-и-диплинк (провайдеры входа, редиректящие обратно в приложение) сочетайте это с тестированием OAuth-колбэков.
Настройка раздражает в первый раз. После этого URL туннеля — просто ещё одна env-переменная в схеме Xcode и Gradle.
О более широких паттернах мобильного тестирования см. мобильное тестирование через localhost-туннель. Запишитесь в лист ожидания PortPreview.