Перейти к основному содержимому

Deep links и mobile bridge

Этот раздел фиксирует рабочий контракт для открытия стрима в WebView и передачи действий пользователя в нативный слой.

URL открытия стрима в WebView

Базовый формат:

https://<client-domain>/live/?wv=1#/translation/<translationId>

Для устройств с вырезом экрана (чёлка / Dynamic Island) добавляйте safe_area=1:

https://<client-domain>/live/?wv=1&safe_area=1#/translation/<translationId>

Fallback, если у клиента нет собственной страницы стримов:

https://app.shopstory.live/<client-name>/?wv=1#/translation/<translationId>
Важно

Кнопка закрытия WebView должна быть реализована в нативном приложении, не внутри страницы стрима.

Контракт событий из WebView

Страница стрима отправляет в нативный слой два события:

  • add-product — пользователь нажал «Купить»
  • open-product — пользователь открыл карточку товара

Payload события передаётся как JSON-строка (результат JSON.stringify). Нативный слой должен распарсить строку для доступа к полям:

  • name
  • feedProductId
  • feedProductGroupId
  • url
  • vendorCode

Подключение обработчиков в нативном приложении

Обработчики нужно зарегистрировать ДО загрузки страницы

Самая частая причина того, что события не приходят — обработчики инициализируются после загрузки страницы или с неправильным именем. Регистрируйте их до вызова loadRequest.

ShopstoryWebViewController.swift
import WebKit

class ShopstoryWebViewController: UIViewController, WKScriptMessageHandler {

var webView: WKWebView!

override func viewDidLoad() {
super.viewDidLoad()

let config = WKWebViewConfiguration()

// Обязательно для inline-воспроизведения видео
// (без этого iOS открывает видео в нативном полноэкранном плеере)
config.allowsInlineMediaPlayback = true
config.mediaTypesRequiringUserActionForPlayback = []

// Регистрация обработчиков — ДО загрузки страницы
config.userContentController.add(self, name: "add-product")
config.userContentController.add(self, name: "open-product")

webView = WKWebView(frame: view.bounds, configuration: config)
view.addSubview(webView)

let url = URL(string: "https://example.ru/live/?wv=1&safe_area=1#/translation/123")!
webView.load(URLRequest(url: url))
}

// Обработка событий от ShopStory
func userContentController(
_ userContentController: WKUserContentController,
didReceive message: WKScriptMessage
) {
guard let jsonString = message.body as? String,
let data = jsonString.data(using: .utf8),
let product = try? JSONSerialization.jsonObject(with: data) as? [String: Any]
else { return }

let feedProductId = product["feedProductId"] as? String ?? ""
let name = product["name"] as? String ?? ""
let url = product["url"] as? String ?? ""

switch message.name {
case "add-product":
// Добавить товар в корзину по feedProductId
CartManager.shared.addProduct(id: feedProductId)
case "open-product":
// Открыть карточку товара в нативном UI
navigator.openProduct(url: url)
default:
break
}
}
}

Что отправляет ShopStory (JS-сторона)

Для справки — так выглядит отправка событий со стороны SDK. Этот код уже встроен в ShopStory, вам его реализовывать не нужно:

shopstory-sdk-internal.js
const PRODUCT = JSON.stringify({
name,
feedProductId,
feedProductGroupId,
url,
vendorCode,
});

// PRODUCT — строка (JSON.stringify), не объект.
// iOS: let data = try JSONSerialization.jsonObject(with: product.data(using: .utf8)!)
window.webkit.messageHandlers['add-product'].postMessage(PRODUCT);
window.webkit.messageHandlers['open-product'].postMessage(PRODUCT);

Отладка

Если события не приходят в нативный слой:

  1. Убедитесь, что URL содержит ?wv=1. Без этого параметра bridge не активируется.
  2. Проверьте, что обработчики зарегистрированы до вызова loadRequest / loadUrl.
  3. Откройте страницу с параметром __DEV__==__DEV__ — SDK покажет в консоли предупреждения вида global.shopstory.addProduct is not a function, которые укажут на незарегистрированные обработчики.
  4. Для быстрой проверки подставьте mock-обработчики через консоль WebView:
debug-mock-handlers.js
// iOS mock
window.webkit = { messageHandlers: {
'add-product': { postMessage: function(p) { console.log('add-product', p); } },
'open-product': { postMessage: function(p) { console.log('open-product', p); } },
}};

// Android mock
window.shopstory = {
addProduct: function(p) { console.log('addProduct', p); },
openProduct: function(p) { console.log('openProduct', p); },
};

После тапа на товар или кнопку «Купить» в консоли должен появиться JSON с payload.

Чек-лист мобильной интеграции

  1. WebView открывает URL с wv=1safe_area=1 для устройств с вырезом).
  2. (iOS) WKWebViewConfiguration включает allowsInlineMediaPlayback = true и mediaTypesRequiringUserActionForPlayback = [].
  3. (Android) WebView.settings.mediaPlaybackRequiresUserGesture = false.
  4. Обработчики add-product и open-product зарегистрированы до загрузки страницы.
  5. При получении add-product приложение добавляет товар в корзину по feedProductId.
  6. При получении open-product приложение открывает карточку товара по url.
  7. Кнопка закрытия WebView реализована нативно.
  8. Для fallback-сценария согласован URL app.shopstory.live/<client-name>.

Частые проблемы

СимптомПричинаРешение
События не приходятОбработчики зарегистрированы после загрузки страницыРегистрировать до loadRequest / loadUrl
События не приходятwv=1 отсутствует в URLДобавить ?wv=1 в query-параметры
Клик «Купить» ничего не делаетИмя interface не совпадаетAndroid: именно "shopstory", iOS: именно "add-product"
Payload приходит как строка, а не объектSDK отправляет JSON.stringifyРаспарсить через JSONSerialization (iOS) или JSONObject (Android)
Контент перекрывается вырезом экранаНе передан safe_area=1Добавить &safe_area=1 в URL
Добавляется не тот товарИспользуется vendorCode вместо feedProductIdДля корзины использовать feedProductId (= <offer id> из фида)
Видео открывается в нативном полноэкранном плеере iOSallowsInlineMediaPlayback не включён в WKWebViewConfigurationДобавьте config.allowsInlineMediaPlayback = true и config.mediaTypesRequiringUserActionForPlayback = []
Видео не воспроизводится автоматически на AndroidmediaPlaybackRequiresUserGesture не отключёнДобавьте webView.settings.mediaPlaybackRequiresUserGesture = false

Связанные разделы: