feat: Android App Links for app.slovo-propovedi.ru #112

Merged
EgorEast merged 9 commits from feat/android-app-links into main 2026-09-26 09:43:00 +00:00
Member

https-ссылки на точные пути /listen и /listen/playlist открываются в приложении (Android App Links, autoVerify); без приложения — веб-версия как раньше.

  • Native: intentFilters в app.config.ts + prebuild-манифест (edddfa24)
  • Данные: трёхуровневый резолв плейлиста (секции → кэш → GET /playlists/{id}) + UUID-гард (a4f4c0cf, 0f458ded)
  • Веб: public/.well-known/assetlinks.json + location ^~ /.well-known/ в nginx (e81056ae)
  • Доки: docs/features/deep-links.md + правки navigation/rest-api/debt (affea876, e1da85e2)
  • Toolchain-фиксы попутно: theme casing-коллизия, commit-msg sed (460d9f26, 62f0c002)
  • Verification: assembleProdRelease OK; on-device forced-flow OK; полный тест — 266/2271…2275 PASS
https-ссылки на точные пути /listen и /listen/playlist открываются в приложении (Android App Links, autoVerify); без приложения — веб-версия как раньше. - Native: intentFilters в app.config.ts + prebuild-манифест (edddfa24) - Данные: трёхуровневый резолв плейлиста (секции → кэш → GET /playlists/{id}) + UUID-гард (a4f4c0cf, 0f458ded) - Веб: public/.well-known/assetlinks.json + location ^~ /.well-known/ в nginx (e81056ae) - Доки: docs/features/deep-links.md + правки navigation/rest-api/debt (affea876, e1da85e2) - Toolchain-фиксы попутно: theme casing-коллизия, commit-msg sed (460d9f26, 62f0c002) - Verification: assembleProdRelease OK; on-device forced-flow OK; полный тест — 266/2271…2275 PASS
playlist deep links on pristine installs could not resolve the UUID (sections only fetched from the listen screen); adds a network tier (GET /playlists/{id} via generated orval client) after sections atom and cache, with a three-tier resolved state so network failure shows "not found" instead of hanging.
Root cause: src/shared/ui/theme/ThemeContext/ contained two modules
differing only by filename case - ThemeContext.ts (type-only,
ThemeContextValue interface) and themeContext.tsx (runtime React
context). On a case-insensitive filesystem (macOS default) resolving
'./themeContext' probes themeContext.ts, which case-insensitively
matches ThemeContext.ts (extension .ts is tried before .tsx), so the
type-only module shadowed the runtime one. That produced TS1149
case-collision errors, TS2305 'no exported member ThemeContext', a
TS2739 error, an ESLint typed-parsing error for themeContext.tsx, and
at jest runtime 'Cannot read properties of undefined (reading
"Provider")' across ~97 suites / 762 tests.

Fix: renamed the type-only file to ThemeContextValue.ts so no two
modules differ only by case, and updated its 4 import sites
(themeContext.tsx, useTheme.ts, useTheme.test.tsx, theme barrel).
Also whitelisted @faker-js in jest transformIgnorePatterns so the
generated API faker mocks (ESM-only @faker-js/faker, type: module)
transform cleanly under jest.

Results before -> after:
- yarn check:types: 9 errors -> 0
- yarn lint: 1 error -> 0 (pre-existing warnings unchanged)
- targeted jest (theme/playlist/player): 14 failed suites / 126 failed
  tests -> 0 failed / 618 passed
- yarn testFinal: ~97 suites / 762 tests failing -> 266 suites /
  2271 tests all passing

Signed-off-by: Bogdan Dunaitsev <bogdan.dunaitsev@umbrellait.com>
Android App Links intent-filter (autoVerify=true) claiming the exact
paths /listen and /listen/playlist on https://app.slovo-propovedi.ru, so
https links to these routes open the installed app instead of the
browser. Configured via expo intentFilters in app.config.ts; the
manifest block is the expo prebuild-regenerated output (android/ is
always regenerated, never hand-edited). Verified with assembleProdRelease
(BUILD SUCCESSFUL).

Signed-off-by: Bogdan Dunaitsev <bogdan.dunaitsev@umbrellait.com>
GNU sed -i without a suffix fails on macOS BSD sed, blocking every
commit on macOS; use the backup-file form, which both seds accept.

Signed-off-by: Bogdan Dunaitsev <bogdan.dunaitsev@umbrellait.com>
assetlinks.json (package ru.slovopropovedi, debug-keystore SHA256 used by
assembleProdRelease per BUILD-LOCAL.md) + nginx ^~ location so
/.well-known/* is served as JSON without redirects or the SPA fallback,
bypassing the dot-deny regex rule.

Signed-off-by: Bogdan Dunaitsev <bogdan.dunaitsev@umbrellait.com>
New deep-links feature doc (intentFilters, assetlinks.json, nginx,
testing via adb, limitations); navigation.md param-format fix (UUID
not JSON) + App Links paragraph; rest-api map update
(playlistControllerFindOne now used by the deep-link network tier);
debt.md scheme-conflict refresh + release-keystore fingerprint entry;
docs map row; BUILD-LOCAL NDK prerequisite.

Signed-off-by: Bogdan Dunaitsev <bogdan.dunaitsev@umbrellait.com>
playlist route params are id-only since 4af131ec (navigateToPlaylist passes
playlist.id); docs corrected in affea876; the entry described a problem
that no longer exists.

Signed-off-by: Bogdan Dunaitsev <bogdan.dunaitsev@umbrellait.com>
fix: harden playlist deep-link id validation and resolver errors
Some checks failed
CI / dco (pull_request) Failing after 6s
CI / checks (pull_request) Has been skipped
0f458ded44
UUID-shape guard at the network tier (external URL input, Issue #45 boundary
rule); 404 vs other-error distinction in the resolver catch (warn vs error)
with intentional notFound collapse documented; stronger late-sections override
test (distinct tier objects); web.md cross-reference.

Signed-off-by: Bogdan Dunaitsev <bogdan.dunaitsev@umbrellait.com>
EgorEast approved these changes 2026-09-26 06:52:38 +00:00
@ -0,0 +5,4 @@
## Обзор
Приложение заявляет **Android App Links** для хоста `https://app.slovo-propovedi.ru` — по клику на ссылку этого домена открывается установленное приложение вместо браузера. Экспо-роутер навигирует сам, без кастомной linking-конфигурации: заявленные пути совпадают с маршрутами `app/`, параметры приходят через `useLocalSearchParams`, как при обычной внутренней навигации.
Owner

Думаю надо сделать чтобы хост брался из переменной окружения EXPO_PUBLIC_WEB_HOSTNAME, а не хардкодился

Думаю надо сделать чтобы хост брался из переменной окружения EXPO_PUBLIC_WEB_HOSTNAME, а не хардкодился
EgorEast marked this conversation as resolved
@ -0,0 +5,4 @@
// Каноническая форма UUID: 8-4-4-4-12 hex-цифр, например 123e4567-e89b-12d3-a456-426614174000
const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
const isUuid = (value: string): boolean => UUID_PATTERN.test(value)
Owner

предлагаю поставить библиотеку uuid и проверять через uuid.validate(...), тем более кастомные плейлисты тоже должны будут генерить uuid

предлагаю поставить библиотеку uuid и проверять через uuid.validate(...), тем более кастомные плейлисты тоже должны будут генерить uuid
EgorEast marked this conversation as resolved
Merge branch 'main' into feat/android-app-links
Some checks failed
CI / dco (pull_request) Failing after 6s
CI / checks (pull_request) Has been skipped
b77ca0a0de
Signed-off-by: egoreast <egor.east@yandex.ru>
Signed-off-by: egoreast <egor.east@yandex.ru>
refactor(playlist): validate playlist id via uuid.validate
Some checks failed
CI / dco (pull_request) Failing after 7s
CI / checks (pull_request) Has been skipped
aa7e174e3f
Signed-off-by: egoreast <egor.east@yandex.ru>
Signed-off-by: egoreast <egor.east@yandex.ru>
docs: document env validation, uuid, and dev App Links
Some checks failed
CI / dco (pull_request) Failing after 4s
CI / checks (pull_request) Has been skipped
585f6f37e9
- EXPO_PUBLIC_* env is required (zod, no defaults) — decisions.md Approved stack row
- uuid.validate replaces regex (decisions.md row)
- deep-links.md — host from env, two-statement assetlinks (dev flavor
  verified), chooser caveat, dev pm get-app-links check
- share.md — WEB_APP_URL built from ENV
- debt.md — https link-capture conflict resolved, entry reduced to custom-scheme
  per-flavor differentiation

Signed-off-by: egoreast <egor.east@yandex.ru>
Signed-off-by: egoreast <egor.east@yandex.ru>
Signed-off-by: egoreast <egor.east@yandex.ru>
Dev and prod shared one custom scheme, so a parallel install made the OS
resolve `slovo-propovedi://` ambiguously. The scheme now lives in flavor
source-set manifests written by withAndroidFlavors (prod `slovo-propovedi`,
dev `slovo-propovedi-dev`); the main manifest carries no custom scheme.

Closes the debt entry "Custom-scheme глубокие ссылки конфликтуют при
параллельной установке dev+prod". https App Links were already per-package
via the multi-package assetlinks.json and are unchanged.

Signed-off-by: egoreast <egor.east@yandex.ru>
Signed-off-by: egoreast <egor.east@yandex.ru>
docs: correct flavor-manifest claim in deep-links
Some checks failed
CI / dco (pull_request) Failing after 5s
CI / checks (pull_request) Has been skipped
347b2b8a45
Signed-off-by: egoreast <egor.east@yandex.ru>
EgorEast added this to the MVP milestone 2026-09-26 08:55:49 +00:00
EgorEast force-pushed feat/android-app-links from 347b2b8a45
Some checks failed
CI / dco (pull_request) Failing after 5s
CI / checks (pull_request) Has been skipped
to d6187ef754
Some checks failed
CI / dco (pull_request) Failing after 4s
CI / checks (pull_request) Has been skipped
2026-09-26 09:19:38 +00:00
Compare
EgorEast force-pushed feat/android-app-links from d6187ef754
Some checks failed
CI / dco (pull_request) Failing after 4s
CI / checks (pull_request) Has been skipped
to bb3db17d5f
All checks were successful
CI / dco (pull_request) Successful in 6s
CI / checks (pull_request) Successful in 2m50s
2026-09-26 09:35:59 +00:00
Compare
Sign in to join this conversation.
No reviewers
No milestone
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Slovo_Propovedi/slovo-propovedi-mobile!112
No description provided.