Перейти до вмісту
Усі статті
8 хв читання

Universal Links проти custom URL scheme: чому список постійно скорочується

Ряд іконок застосунків, що скорочується, поруч із єдиним шляхом Universal Link на iOS

Посилання, яке минулого кварталу відкривало Telegram чи Instagram напряму, сьогодні може викинути того самого відвідувача в Safari — і у вашому коді нічого не змінилося. Це не деградація. Вендори свідомо виводять custom URL scheme з ужитку, і не один із них прямо пише про це у власній документації.

Список застосунків, у які можна надійно перейти з посилання, скорочується — і скорочується тому, що компанії, яким належать ці застосунки, вирішили: scheme — це вразливість, а не зручність. Розуміння того, у кого scheme ще працює, чому решта її втратили і що насправді дає Universal Link натомість, змінює всю логіку побудови rewrite-ланцюжка — особливо на iOS, де немає аналога intent://, на який можна спертися, коли scheme не спрацьовує.

Scheme, які вендори прибрали — їхніми ж словами

Жодна з них не є недороблена реалізація, яку хтось колись довершить. Кожен вендор у власній документації чи довідковому центрі пояснив, чому переписувати посилання більше нема на що.

ЗастосунокСтатусПричина, яку заявляє вендор
LINEline:// виведено з ужиткуЩоб зупинити атаки захоплення, коли запускається не той застосунок
PinterestНіколи не публікувавсяУ власному довідковому центрі заявляє, що не публікує шаблони для третіх сторін
YouTubeНемає документованої schemeАктуальна документація Google каже, що Universal Links вже відкривають застосунок на iOS 9+
XНемає підтримуваної schemeDeep link для складання DM існує, але потребує ручної відправки та платного рівня Account Activity API, який виводять з ужитку
SnapchatНемає schemeУ повному переліку продуктів платформи розробника немає жодного API для месенджингу чи чату
WeChatНемає клікабельного посиланняМеханізм передачі payload — це QR scene_id, а не URL
RedditНемає документованої scheme

Аргументація LINE — найпряміше формулювання в усьому цьому списку того, чому scheme є поверхнею атаки, а не просто зручністю. Реєстрація scheme — це обіцянка, що на неї відповість саме потрібний застосунок, і в самому механізмі нічого не заважає іншому застосунку зареєструвати той самий рядок першим. Виведення scheme з ужитку закриває цю діру повністю, замість того щоб латати її застосунок за застосунком.

X — найчистіший приклад вендора, який просто ніколи не будував таку двер взагалі. Deep link для складання DM існує на папері, але потребує ручної відправки від відвідувача і стоїть за платним рівнем Account Activity API, який сам по себе виводиться з ужитку. Для маршрутизації це означає те саме, що й відсутність scheme: усе, що ви побудуєте проти нього, не переживе зіткнення з вимогою рівня акаунта.

Pinterest, Snapchat, WeChat і Reddit доповнюють список трьома різними варіаціями однієї відмови. Довідковий центр Pinterest прямо каже, що не публікує шаблони для сторонніх розробників — реверс-інжинірити недокументовану scheme нема сенсу, бо позиція компанії в тому, що її й не мало існувати. У платформи розробника Snapchat у переліку продуктів просто немає поверхні для месенджингу чи чату, тож немає й API, під який scheme могла б підлаштовуватись. Аналог deep link у WeChat — це QR scene_id, який потребує камери, наведеної на екран, а не URL, який може нести браузер, — це зовсім інший механізм, а не scheme під іншою назвою. Reddit не документує нічого в жодну сторону, тобто це радше категорія «невідомо», ніж «підтверджено відсутнє», але для посилання, яке ви ось-ось запустите в продакшн, практична поведінка ідентична: не будуйте rewrite-правило на scheme, яку ніхто не підтвердив.

Якщо читати це як тенденцію, а не як список, закономірність тримається на всіх семи рядках: вузьке покриття на iOS тут здебільшого — свідомий крок галузі в бік Universal Links, а не пропуск, який хтось забув закрити. Це варто знати до того, як ви витратите вечір на пошук scheme LINE у декомпільованому APK — компанія, якій вона належить, уже написала, що не хоче тримати цю двер відкритою.

Що ще працює

Кілька rewrite-правил підтверджено робочі — див. повну довідкову таблицю того, які scheme досі працюють, — але кожне має власну граматику. Немає спільного шаблону, який можна застосувати до всіх застосунків так, як package=<package> покриває будь-який застосунок на Android.

Публічний URLCustom schemeПримітка
https://t.me/<bot>?start=<payload>tg://resolve?domain=<bot>&start=<payload>
https://t.me/<channel>tg://resolve?domain=<channel>
https://instagram.com/<username>instagram://user?username=<username>
https://facebook.com/profile.php?id=<n>fb://profile/<n>потрібен саме числовий id — Graph API відмовляє в пошуку за username (помилка #803)
https://open.spotify.com/track/<id>spotify:track:<id>двокрапки, а не слеші
https://twitch.tv/<channel>twitch://stream/<channel>
https://twitch.tv/<ch>/v/<id>twitch://video/v<id>id VOD потребує префікса v

В Instagram — своя версія проблеми Facebook, у зворотному напрямку. URL посту чи reels несе shortcode, а scheme потребує внутрішній числовий media id, і публічного шляху з одного в інший немає. У Facebook — дзеркальна проблема: fb://profile/ приймає лише числовий id, а Graph API відмовляється резолвити username у нього, тож URL профілю, зібраний скрейпінгом, а не знайдений у Business Manager, — це глухий кут.

TikTok заслуговує на окремий абзац, а не рядок у таблиці, бо направити TikTok-трафік не на ту scheme id — це помилитись тихо, а не голосно. Її scheme на iOS — snssdk1233, за власною SDK-документацією TikTok. snssdk1128 належить Douyin — іншому застосунку, з іншого стору, для іншого ринку. Rewrite-правило, побудоване проти неправильного id, або не відкриває нічого, або відкриває Douyin для тієї невеликої частки відвідувачів, у кого він випадково встановлений, — і жоден із цих збоїв не виглядає як звичайна одруківка, коли ви дивитесь на нього в дебагері.

Custom scheme детермінована: будь-яка сторінка, що знає синтаксис, може її викликати, і застосунок або відповідає, або ні — хоча відповісти не те саме, що утримати відвідувача всередині. Universal Link побудована інакше. Це https:// URL постійно, тож вона деградує безпечно — браузер завжди зможе її відкрити, — але чи відкриється також застосунок, вирішує від імені вендора операційна система, а не безпосередньо ваше посилання. Саме на цей обмін пішли LINE, Pinterest, X і YouTube, коли відмовились підтримувати власну scheme: передати рішення «відкривати застосунок чи ні» механізму асоціації платформи замість того, щоб самим утримувати захищену двер.

Власна документація Google прямо каже, що Universal Links покривають YouTube з часів iOS 9 — це не нещодавній обхідний шлях, а механізм, задуманий як основний практично весь час існування платформи, і YouTube просто ніколи не потребував власної scheme, відколи він з’явився. Але «рішення за ОС» діє в обидва боки, щойно ви на нього покладаєтесь. Те саме посилання, відкрите зсередини вбудованого браузера іншого застосунку, не обов’язково отримає таку саму обробку, як відкрите з Safari чи Messages — контекст, у якому стався тап, входить у те, що зважує система, а не лише сам рядок URL. Якщо помітна частка вашого трафіку приходить через вбудований браузер, а не нативний, — це якраз трафік, що найімовірніше приземлиться в браузері замість застосунку, той самий трафік, над яким Universal Link дає вам найменше контролю.

Один рядок проти зведення правил для кожного застосунку

Android звужує все це до одного механізму. Синтаксис intent потребує рівно одну змінну на застосунок:

intent://host/path#Intent;scheme=https;package=<package>;end

Правильно вказали package — rewrite-правило готове. Instagram, Spotify, Twitch і Telegram — усі резолвяться через ідентичну граматику на Android, просто з іншим рядком у тому самому місці.

iOS не дає такого скорочення. Потрібна і scheme, і rewrite-правило, побудоване під власну структуру URL цього застосунку, а форма змінюється від застосунку до застосунку: боту потрібен ?start=, каналу — ні; Spotify хоче двокрапки там, де Twitch хоче слеші; VOD Twitch потребує префікса v перед id, якого посилання на живий канал ніколи не несе. Сім робочих scheme у таблиці вище означають сім окремих rewrite-правил, а не одне правило із сімома підстановками.

Ця асиметрія і є справжньою ціною скорочення списку scheme. Річ не лише в тому, що щороку менше застосунків відповідають на custom scheme — річ у тому, що кожен із тих, хто ще відповідає, вимагає щось трохи інше від попереднього, і спільного шаблону для всіх застосунків на iOS фактично ніколи не було так, як на Android. Рівень маршрутизації, який трактує кожне rewrite-правило як власне редаговане правило, а не одну загальну схему з підставленим package, — це те, що не дає списку, який скорочується, перетворитися на купу непрацюючих редиректів щоразу, коли вендор передумає. Streams від DarkCore тримає ланцюжки package Android і scheme-rewrite iOS як окремі правила по кожному застосунку, тож виведення scheme одного застосунку з ужитку — це зміна конфігурації на вашому боці, а не редеплой.

Це не одноразова інвентаризація. Scheme, підтверджена робочою сьогодні, підтверджена станом на останній раз, коли хтось справді торкнувся її на реальному пристрої, а не як постійна властивість застосунку — власна історія LINE це доводить, адже line:// працювала роками до того, як з’явилось повідомлення про виведення з ужитку. Ставтесь до кожного рядка в обох таблицях вище як до того, що варто періодично перевіряти на реальному пристрої, а не як до факту, який один раз захардкодили й забули, і ставтесь до «підтверджено відсутнє» так само, як до «підтверджено робоче»: як до поточної позиції вендора — а це саме те, що вендори змінюють, не питаючи ваш редирект-ланцюжок.

  • universal links vs url scheme
  • ios deep link
  • line deprecated scheme
  • app links vs deep links