<!-- Canonical: https://docs.shopstory.live/docs/guide/mobile-sdk -->

# Mobile SDK

ShopStory Mobile SDK встраивает product video, полный плеер и продолжение просмотра в приложение ритейлера. Один и тот же сценарий работает для текущего live и опубликованной записи. Каталог, карточка товара, навигация, авторизация, корзина и подтверждение покупки остаются в приложении клиента.

Сейчас SDK подключается к native iOS и Flutter-приложениям на iOS. Для native iOS ShopStory передаёт точную версию SDK; для Flutter — совместимую пару Flutter/native пакетов. Требования к toolchain и версии фиксируются в integration handoff.

Публичная и проектная документация

На этой странице описаны пользовательский сценарий, ответственность сторон и обязательная приёмка. Доступ к пакетам, команды установки, клиентские routes, `applicationId`, тестовые товары, rollout и rollback команда интеграции получает отдельно.

## Когда выбирать Mobile SDK

Mobile SDK подходит, когда видео должно быть частью нативного покупательского пути:

- точка входа в live или запись находится на карточке товара;
- полный плеер открывается внутри приложения;
- после выбора товара приложение открывает собственную PDP;
- текущая медиасессия продолжается в компактном follow-me player;
- кнопка «Купить» вызывает нативный route или согласованную cart-команду;
- ShopStory не должен получать токены авторизации, профиль покупателя или содержимое корзины.

Для web-сайта используйте [Web SDK](<https://docs.shopstory.live/docs/guide/web-sdk>). Для собственного списка эфиров поверх read-only данных используйте [Public API](<https://docs.shopstory.live/docs/guide/quickstart>).

## Возможности

| Сценарий | Поведение |
| --- | --- |
| Product entrypoint | SDK проверяет связанный live/VOD по строковому `feedProductId` и показывает preview только для доступного контента. |
| Полный плеер | ShopStory управляет WebView-плеером, загрузкой, media lifecycle и проверкой origin. |
| Product handoff | Типизированное действие передаётся приложению; нативный router открывает товар по `feedProductId`. |
| Follow-me | Та же медиасессия продолжается поверх разрешённых экранов без перезапуска. |
| Cart safety | Host app сообщает текущую поверхность и после смены route применяет решение runtime policy, чтобы скрыть player на cart, checkout, payment, auth и других заблокированных экранах. |
| Управление запуском | Remote policy может отключить SDK-owned surfaces без выпуска новой версии приложения. |

Системный Picture-in-Picture и фоновое воспроизведение в этот контракт не входят. Когда приложение уходит в background, воспроизведение ставится на паузу.

## Платформенная поставка

| Контур | Формат интеграции | Что получает команда клиента |
| --- | --- | --- |
| Native iOS | `ShopStoryPlayerSDK` через Swift Package Manager или CocoaPods | Доступ к пакету, точная версия, руководство к этой версии, checklist приёмки и зафиксированная версия для отката. |
| Flutter on iOS | `shopstory_player_flutter` и совместимая native iOS dependency | Зафиксированная пара версий, Flutter host contract и iOS acceptance checklist. |
| Другой mobile stack | Согласовывается до оценки разработки | Отдельное решение по runtime, ownership и срокам; web-фрагменты не считаются готовым mobile SDK. |

Команда клиента получает точную версию пакета и доступ через integration handoff. Access tokens и координаты репозитория публично не размещаются.

### Техническая совместимость

| Контур | Текущий baseline для оценки |
| --- | --- |
| Native iOS | iOS 15+, Xcode 16+, Swift Package Manager или CocoaPods. PDP entrypoint и follow-me предоставляются как SwiftUI-компоненты; full-screen player монтируется как UIKit view. |
| Flutter on iOS | Flutter 3.24+, Dart 3.5+ и iOS 15+. Flutter adapter и native dependency фиксируются совместимой парой. |

Если native host полностью построен на UIKit, включите в оценку SwiftUI hosting layer для PDP и follow-me. Точные требования выбранной версии пакета из integration handoff имеют приоритет над этим baseline.

## Архитектура и ответственность

```mermaid
flowchart LR
    A[Native PDP] -->|feedProductId| B[ShopStory Mobile SDK]
    B --> C{Есть live или VOD?}
    C -->|Нет| D[Не показывать entrypoint]
    C -->|Да| E[Preview и полный player]
    E -->|Product action| F[Native router или cart command]
    F --> G[Merchant catalogue, stock и cart API]
    F --> H[Follow-me на разрешённом экране]
```

| ShopStory SDK | Приложение ритейлера |
| --- | --- |
| Проверяет video-state одного товара | Передаёт стабильный `feedProductId` из каталога |
| Показывает entrypoint, preview и player | Выбирает место на PDP и нативный full-screen container |
| Валидирует player URL, origin и сообщения WebView | Не создаёт собственный bridge поверх SDK |
| Управляет одной медиасессией и предоставляет policy для follow-me | Сообщает текущую commerce-поверхность и после route transition синхронизирует с policy активную сессию |
| Передаёт типизированное product action | Разрешает товар в своём каталоге и выполняет route/cart command |
| Применяет remote policy и безопасно скрывает UI при ошибке | Выпускает, наблюдает и откатывает зафиксированную версию пакета |

### Встраивание и брендинг

SDK работает внутри приложения ритейлера. Приложение сохраняет свои PDP, navigation, cart, auth и native analytics. ShopStory настраивает внешний вид web-плеера для проекта. До приёмки стороны фиксируют, какие элементы можно менять; остальные theme overrides в контракт не входят.

## Путь интегратора

1. Подтвердить mobile stack, поддерживаемую версию ОС и способ подключения пакета.
2. Зафиксировать поле каталога приложения, равное `feedProductId` в [товарном фиде ShopStory](<https://docs.shopstory.live/docs/guide/feed-requirements>).
3. Получить `applicationId`, read-only package access и exact version.
4. Настроить SDK один раз при запуске приложения без блокировки startup.
5. Передавать текущую commerce-поверхность из существующего router/state owner и синхронизировать с policy активную playback session после initial route, push, replace, pop и смены tab.
6. На PDP запрашивать video-state текущего товара и удалять entrypoint при смене товара или закрытии экрана.
7. Подключить native product route. Cart handler добавляется, только если проекту нужна прямая корзина.
8. Пройти приёмку live, VOD, no-content, network failure, background, cart и rollback.

## Состав работ для оценки

| Work package | Работа приложения | Входные данные |
| --- | --- | --- |
| Dependency | Подключить и зафиксировать SDK; настроить CI-доступ | Exact package version и требования к toolchain |
| Bootstrap | Вызвать `configure` один раз и сохранить host app независимым от результата | `applicationId` и test environment |
| Navigation | Сопоставить routes с `ShopStorySurface`; применить policy после каждого перехода | Route-to-surface map, включая tabs и nested navigation |
| PDP | Передать `feedProductId`, управлять cancellable lookup и состоянием entrypoint | Test products с live, VOD и no-content |
| Playback | Удерживать одну session/coordinator, full-screen container и follow-me container | Выбранная UIKit/SwiftUI topology |
| Product action | Проверить ID и открыть native PDP; при необходимости вызвать cart-команду | Product route и отдельное решение по direct cart |
| Release | Прогнать Release build, physical-device acceptance и rollback | Device matrix, checklist и предыдущая принятая версия |

Оценка direct cart требует контракта merchant API и fallback. Оценка базового сценария с открытием native PDP от cart API не зависит.

## Идентификатор товара

`feedProductId` связывает товарный фид, ShopStory и каталог приложения. Храните его строкой: ведущие нули и буквенные символы могут быть значимыми.

```text
merchant catalogue product
        │
        ├── feedProductId в товарном фиде
        ├── feedProductId на native PDP
        └── feedProductId в product action из player
```

Не используйте внутренний ShopStory `productId` для нативной маршрутизации. URL из product action является справочным полем и не должен управлять переходом внутри приложения.

## Минимальный host contract

Ниже показан минимальный host contract. Сверяйте сигнатуры с документацией к установленной версии пакета.

### Native iOS

```swift
import ShopStoryPlayerSDK

private let shopStory = ShopStorySDK.shared
private var shopStoryConfigured = false
private var productStateRequest: ShopStoryProductStreamStateRequest?

func configureShopStory() {
    do {
        try shopStory.configure(
            applicationId: applicationId,
            hostAppVersion: Bundle.main.object(
                forInfoDictionaryKey: "CFBundleShortVersionString"
            ) as? String
        )
        shopStoryConfigured = true
    } catch {
        shopStoryConfigured = false
        hideShopStoryEntrypoint()
        reportIntegrationError(error)
    }
}

func loadShopStory(for product: Product) {
    guard shopStoryConfigured else {
        hideShopStoryEntrypoint()
        return
    }

    shopStory.setCurrentSurface(.productDetail)
    productStateRequest?.cancel()
    productStateRequest = shopStory.loadProductStreamState(
        feedProductId: product.feedProductId
    ) { result in
        switch result {
        case let .success(.active(entrypoint)):
            showShopStoryEntrypoint(entrypoint)
        case .success(.unavailable):
            hideShopStoryEntrypoint()
        case let .failure(error):
            hideShopStoryEntrypoint()
            reportIntegrationError(error)
        }
    }
}
```

Вызывайте конфигурацию один раз при запуске приложения. Ошибка оставляет ShopStory UI скрытым и не прерывает запуск host app. При смене PDP отменяйте предыдущий request. `.unavailable` означает отсутствие доступного видео; `.failure(error)` передавайте в диагностический logger приложения. Полученный entrypoint передавайте в coordinator целиком; не собирайте player URL вручную.

### Full-screen player и follow-me в native iOS

Lookup из предыдущего примера только получает entrypoint. Для воспроизведения host app выполняет ещё шесть действий:

1. Root integration owner создаёт и удерживает одну playback session, один `ShopStoryPlayerCoordinator` и floating-player controller.
2. Full-screen container вызывает `coordinator.attach(to:)`, затем `coordinator.load(entrypoint:)`.
3. Coordinator delegate принимает типизированные product actions и lifecycle events.
4. При переходе из player в native PDP host отсоединяет player view без teardown, открывает PDP и монтирует ту же сессию в follow-me container.
5. После каждого route transition host вызывает `setCurrentSurface` и применяет `shouldShowFollowMePlayer()` к активной сессии: показывает или скрывает container и синхронизирует playback state.
6. `teardown()` вызывается при закрытии, завершении сессии или remote disable. Product handoff между full-screen и follow-me не завершает сессию.

Создавайте `ShopStoryPlaybackOwner` после успешного `configure`, когда начинается активная playback session. Root integration owner приложения удерживает этот объект до `stop()`.

player-owner.swift

```swift
import ShopStoryPlayerSDK
import UIKit

@MainActor
final class ShopStoryPlaybackOwner: ShopStoryPlayerCoordinatorDelegate {
    private let sdk = ShopStorySDK.shared
    private let floatingController = ShopStorySDK.shared.makeFloatingPlayerController()
    private(set) lazy var coordinator = ShopStoryPlayerCoordinator(delegate: self)

    func start(entrypoint: ShopStoryProductDetailEntrypoint, in container: UIView) {
        coordinator.attach(to: container)
        coordinator.load(entrypoint: entrypoint)
    }

    func shopStoryPlayerCoordinator(
        _ coordinator: ShopStoryPlayerCoordinator,
        didReceive action: ShopStoryProductAction
    ) {
        guard let feedProductId = action.payload.feedProductId,
              !feedProductId.isEmpty else {
            reportInvalidProductAction(action)
            return
        }

        handleProductAction(action.kind, feedProductId: feedProductId)
    }

    func reconcile(
        surface: ShopStorySurface,
        mediaOrientation: ShopStoryMediaOrientation
    ) {
        sdk.setCurrentSurface(surface)

        if sdk.shouldShowFollowMePlayer() {
            let geometry = sdk.followMeGeometry(mediaOrientation: mediaOrientation)
            floatingController.show(using: geometry)
            coordinator.resumePlayback()
        } else {
            coordinator.pausePlayback(reason: .surfaceBlocked)
            floatingController.suppress()
        }
    }

    func shopStoryPlayerCoordinator(
        _ coordinator: ShopStoryPlayerCoordinator,
        didUpdate event: ShopStoryPlayerLifecycleEvent
    ) {
        handlePlayerLifecycle(event)
    }

    func stop() {
        floatingController.close()
        coordinator.teardown()
    }
}
```

`handleProductAction`, `handlePlayerLifecycle` и native containers принадлежат приложению. Versioned guide из integration handoff содержит полную схему удержания session, detach, follow-me mount и release.

### Flutter on iOS

```dart
final shopStory = ShopStoryPlayerFlutter.instance;
var shopStoryConfigured = false;

if (shopStory.isSupportedPlatform) {
  try {
    await shopStory.configure(applicationId: applicationId);
    shopStoryConfigured = true;
  } catch (error, stackTrace) {
    FlutterError.reportError(
      FlutterErrorDetails(exception: error, stack: stackTrace),
    );
  }
}

final observer = ShopStoryNavigatorObserver(
  surfaceForRoute: mapRouteToShopStorySurface,
);

MaterialApp(
  navigatorObservers: [observer],
  home: const AppRoot(),
);

final productScreen = shopStoryConfigured
    ? ShopStoryProductDetailScope(
        feedProductId: product.feedProductId,
        child: ProductDetailView(product: product),
      )
    : ProductDetailView(product: product);
```

Один root `Navigator` должен использовать observer. Для declarative router, tabs и nested navigator передавайте текущую поверхность из существующего state owner через `setCurrentSurface`.

До открытия ShopStory UI зарегистрируйте product-action handlers в integration root. `onOpenProduct` возвращает результат после принятого native route. `onAddProduct` нужен только для прямой корзины и возвращает успех после подтверждённой cart-команды. Регистрацию храните до teardown и затем вызовите `dispose()`.

## Пользовательский сценарий

Короткие ролики показывают поведение интеграции на нейтральных данных. Они воспроизводятся без звука; каждый сценарий описан под роликом. Клиентских данных, application IDs и внутренних имён протокола в медиа нет.

[Медиа](<https://docs.shopstory.live/media/mobile-sdk/demos/pdp-to-player.a6ba46f601f7.mp4>)

**PDP → player**Video entrypoint появляется для связанного товара и открывает live или запись.

[Медиа](<https://docs.shopstory.live/media/mobile-sdk/demos/product-handoff-follow-me.3ac1e9d6e851.mp4>)

**Product handoff**Приложение открывает свою PDP, а SDK переносит ту же сессию в follow-me.

[Медиа](<https://docs.shopstory.live/media/mobile-sdk/demos/follow-me-lifecycle.431baa593929.mp4>)

**Lifecycle policy**Follow-me скрывается на cart/checkout и возвращается на разрешённом экране.

## Product actions, корзина и остаток

Плеер передаёт намерение открыть товар или добавить его в корзину. Приложение и commerce-система ритейлера проверяют товар и выполняют действие.

1. Приложение получает типизированное действие и проверяет непустой `payload.feedProductId`. При отсутствии ID commerce action отклоняется без перехода и изменения корзины.
2. Native catalogue подтверждает, что товар существует и доступен в нужном регионе или магазине.
3. Для прямой корзины приложение вызывает свой stock/cart API с текущей сессией покупателя.
4. Merchant UI считает cart-команду успешной только после подтверждённого изменения корзины.
5. При отсутствии стабильной cart-команды действие открывает нативную PDP.

В native iOS действие приходит через delegate без completion. Flutter handler возвращает адаптеру типизированный результат для host-side lifecycle, но этот результат не передаётся обратно в embedded player как подтверждение корзины. Поэтому состояние кнопки в player и аналитическое событие не доказывают изменение корзины.

По умолчанию действие открывает нативную PDP. Прямую корзину включайте только по контракту из документации к поставленной версии. Подтверждённый результат добавления показывает интерфейс ритейлера. Плеер ShopStory не хранит состояние корзины.

Проверка остатка внутри cart-команды приложения входит в native cart flow. Наличие по выбранному магазину до нажатия кнопки строится на том же контракте, что и в [Web SDK](<https://docs.shopstory.live/docs/guide/product-availability>): приложение получает список `feedProductId` текущего эфира и возвращает наличие из своей commerce-системы. Точные сигнатуры для каждой платформы входят в документацию к поставленной версии пакета; объект `actions` из web-страницы в приложение не переносится.

Между синхронизациями фида данные каталога устаревают, поэтому окончательное решение о покупке остаётся за cart-командой приложения.

## Lifecycle и безопасное поведение

- SDK сохраняет одну playback session при переходе full-screen → native PDP → follow-me.
- Приложение сообщает реальную видимую поверхность после push, replace, pop и изменения tab, затем синхронизирует активную сессию с SDK policy.
- Неизвестная поверхность блокируется до явного разрешения.
- Cart, checkout, payment, auth и profile исключаются из follow-me по runtime policy.
- При background media ставится на паузу; отдельный background audio contract отсутствует.
- При закрытии или отключении policy container удаляется, player teardown завершается, handlers освобождаются.
- Ошибка SDK оставляет PDP, navigation, cart и checkout приложения рабочими.

`unavailable` — штатное состояние: для товара нет доступного live/VOD, поэтому entrypoint не показывается. При технической ошибке приложение записывает typed error в свой диагностический logger и удаляет пустой блок с PDP.

## Security и privacy boundary

SDK владеет WebView-конфигурацией и проверяет HTTPS, exact origin, main-frame navigation, схему и размер сообщения. Не создавайте параллельный `WKWebView`, JavaScript bridge или URL-конструктор по фрагментам этой страницы.

Native SDK использует только данные, необходимые для video integration:

- `applicationId` проекта;
- platform, SDK version и host app version;
- текущий `feedProductId`;
- player/preview URLs из ответа ShopStory;
- runtime policy и локальные lifecycle diagnostics.

Не передавайте в SDK access tokens, session cookies, платёжные данные, профиль, точную геопозицию, advertising ID или содержимое корзины. Web-плеер отправляет собственные события аналитики ShopStory; приложение ритейлера отвечает за аналитику нативной навигации и подтверждённого результата в своём commerce-интерфейсе.

Подробнее: [Безопасность интеграции](<https://docs.shopstory.live/docs/operations/security>).

## Приёмка перед выпуском

- Exact package version зафиксирована в lockfile; для Flutter там же зафиксирована совместимая native dependency.
- `feedProductId` совпадает в mobile catalogue и товарном фиде, включая ведущие нули.
- Для товара с видео entrypoint открывает live и VOD; для товара без видео UI отсутствует без пустого места.
- Смена товара отменяет предыдущий lookup и не показывает устаревший preview.
- Product action открывает правильную native PDP.
- Для прямой корзины merchant UI показывает success только после подтверждения cart API; состояние кнопки в player не используется как подтверждение.
- Follow-me сохраняет ту же сессию, соблюдает safe area и скрывается на cart/checkout/payment/auth/profile.
- Background ставит media на паузу; close и remote disable полностью освобождают player UI.
- Потеря сети и некорректная конфигурация не блокируют PDP, navigation или checkout.
- Проверены accessibility labels, Dynamic Type/масштаб текста и целевые orientation.
- На поддерживаемом физическом iPhone проверены live, VOD, звук, background/foreground, product handoff и follow-me.
- Release build и rollback на предыдущую принятую exact version проверены до rollout.

## Integration handoff

Команда ShopStory передаёт назначенным инженерам клиента:

- package repository access без секретов в исходном коде;
- exact immutable version; для Flutter — совместимую пару Flutter/native зависимостей;
- requirements конкретной версии;
- `applicationId`, test products и ожидаемые live/VOD states;
- route-to-surface map и выбранный cart contract;
- acceptance checklist, rollout, rollback и канал поддержки.

При обращении в поддержку приложите SDK version, Xcode/Flutter/iOS version, host app version, `applicationId`, затронутый `feedProductId`, время проблемы и типизированную ошибку. Не отправляйте auth tokens, session cookies или полные production logs.

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

- [Методы интеграции](<https://docs.shopstory.live/docs/guide/integration-methods>)
- [Требования к товарному фиду](<https://docs.shopstory.live/docs/guide/feed-requirements>)
- [Mini-player и video-state](<https://docs.shopstory.live/docs/guide/mini-player>)
- [Добавление товара в корзину](<https://docs.shopstory.live/docs/guide/add-to-cart>)
- [Интеграция с интернет-магазином](<https://docs.shopstory.live/docs/guide/custom-commerce-integrations>)
- [Безопасность](<https://docs.shopstory.live/docs/operations/security>)
