Руководства

Чат в приложении (WebView)

Встройте полноценный чат-виджет в любое мобильное или десктопное приложение через WebView: все функции и автоматические обновления.

Самый быстрый способ добавить поддержку клиентов в нативное приложение — загрузить нашу полноэкранную страницу виджета в WebView. Вы получаете ровно тот же виджет, что и на сайте: главная, сообщения, центр помощи, поиск, эмодзи, вложения, отметки времени, отметки о прочтении, завершение диалога, — и он обновляется автоматически. После интеграции приложению больше не нужны новые релизы ради изменений в виджете.

Быстрый старт

Откройте этот URL в WebView. Для авторизованного пользователя обязательно передайте externalId и email (а лучше и name), иначе оператор всегда будет видеть только анонимного посетителя. Каждое значение должно быть закодировано для URL:

https://<your-domain>/api/widget-embed?appId=YOUR_APP_ID&externalId=USER_ID&email=USER_EMAIL&name=USER_NAME

Анонимный вариант используйте, только если пользователь не вошёл в систему:

https://<your-domain>/api/widget-embed?appId=YOUR_APP_ID

⚠️ Оператор видит «Анонимный посетитель» без email? Это ошибка интеграции номер один: в URL вашего WebView нет email/externalId. Сервер не может их придумать — их должно передать приложение. Если идентификатор посетителя выглядит как anon-…, значит, ничего не было передано.

JavaScript / любой WebView (не Flutter)

Electron, десктопные оболочки, React Native или нативный WKWebView, где URL вы задаёте сами, — собирайте URL на JS. URLSearchParams сам закодирует каждое значение:

// Build the support URL for the signed-in user before opening the WebView.
function buildSupportUrl(domain, user) {
  const params = new URLSearchParams({
    appId: 'YOUR_APP_ID',
    platform: 'windows',            // Strongly recommended: decides which help articles this visitor sees
                                    // ios / macos_appstore / macos / android / windows / web
    // locale: userSelectedLanguage, // Optional: only if your app has its own language switch; also add forceLocale: '1'
                                    // Omitted = follow the device language (covers the App Store's 50 localizations)
    // — Always pass these for signed-in users, or agents only see an "anonymous visitor" —
    externalId: user.id,            // The user's unique ID in your system
    email: user.email,              // The user's email (important)
    name: user.name || '',          // The user's name (optional)
    // hmac: user.hmac,             // Optional: computed on your server to prevent impersonation (see HMAC below)
    // attrs: JSON.stringify({ plan: user.plan, expiresAt: user.expireAt }), // Optional: custom attributes
  });
  return `https://${domain}/api/widget-embed?${params.toString()}`;
}

// Usage: load the returned url in your WebView (instead of the old ?appId=...-only URL).
const url = buildSupportUrl('askais.com', currentUser);
myWebView.loadURL(url); // Electron: win.loadURL(url); native: load this URL

Flutter (webview_flutter)

Добавьте webview_flutter: ^4.x в pubspec.yaml, затем:

import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';

class SupportPage extends StatefulWidget {
  const SupportPage({super.key});
  @override
  State<SupportPage> createState() => _SupportPageState();
}

class _SupportPageState extends State<SupportPage> {
  late final WebViewController _controller;

  @override
  void initState() {
    super.initState();
    // For signed-in users always include externalId + email, or agents see an "anonymous visitor" (no name / email).
    // Pass only appId + locale when the user is not signed in. Uri.https URL-encodes the values.
    final uri = Uri.https('askais.com', '/api/widget-embed', {
      'appId': 'YOUR_APP_ID',
      'platform': Platform.isIOS ? 'ios' : 'android', // Strongly recommended: decides which help articles are visible
      // No locale = follow the device language (covers the App Store's 50 localizations).
      // Only pass it if your app has its own language switch, together with 'forceLocale': '1':
      // 'locale': appSettings.selectedLanguage,
      'externalId': user.id, // The user's unique ID in your system (important)
      'email': user.email, // The user's email (important)
      'name': user.name, // Optional
      // 'hmac': hmacFromYourServer, // Optional: computed on your server to prevent impersonation
    });
    _controller = WebViewController()
      ..setJavaScriptMode(JavaScriptMode.unrestricted)
      ..setBackgroundColor(Colors.white)
      ..loadRequest(uri);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Support')),
      // Full-screen: drop the appBar and use your own close button.
      body: SafeArea(child: WebViewWidget(controller: _controller)),
    );
  }
}

// Open it from wherever your "Support" button lives:
// Navigator.push(context,
//   MaterialPageRoute(builder: (_) => const SupportPage()));

Для WebView на iOS/Android в других стеках (Swift WKWebView, Kotlin WebView, React Native react-native-webview) принцип тот же: загрузить URL и включить JavaScript.

Нативные iOS и Android

Никакой SDK устанавливать не нужно: вы собираете тот же URL и передаёте его в WebView. Обратите внимание на Mac: сборка для App Store и DMG-сборка работают в одной и той же операционной системе, поэтому значение должно задаваться флагом сборки, а не проверкой во время выполнения.

// ── iOS (Swift / WKWebView) ────────────────────────────────
// On Mac Catalyst / macOS, set platform to macos_appstore or macos.
#if targetEnvironment(macCatalyst)
let platform = "macos_appstore"   // App Store build; a DMG build from your website passes "macos"
#else
let platform = "ios"              // iPhone and iPad are both ios
#endif

var comps = URLComponents(string: "https://\(domain)/api/widget-embed")!
comps.queryItems = [
    .init(name: "appId", value: "YOUR_APP_ID"),
    .init(name: "platform", value: platform),
    .init(name: "externalId", value: user.id),     // Required for signed-in users
    .init(name: "email", value: user.email),       // Required for signed-in users
    .init(name: "name", value: user.name),
    // Only pass these two if your app has its own language switch:
    // .init(name: "locale", value: settings.language),
    // .init(name: "forceLocale", value: "1"),
]
webView.load(URLRequest(url: comps.url!))          // URLComponents URL-encodes the values


// ── Android (Kotlin / WebView) ─────────────────────────────
val url = Uri.parse("https://$domain/api/widget-embed")
    .buildUpon()
    .appendQueryParameter("appId", "YOUR_APP_ID")
    .appendQueryParameter("platform", "android")
    .appendQueryParameter("externalId", user.id)
    .appendQueryParameter("email", user.email)
    .appendQueryParameter("name", user.name)
    // .appendQueryParameter("locale", settings.language)
    // .appendQueryParameter("forceLocale", "1")
    .build()

webView.settings.javaScriptEnabled = true
webView.settings.domStorageEnabled = true          // The widget uses localStorage to remember the visitor
webView.loadUrl(url.toString())

Параметры URL

  • appId (обязательно) — App ID ваших входящих.
  • locale — язык интерфейса. Почти всегда его не нужно передавать: тогда виджет следует языку устройства, который сообщает WebView, и именно этого ждут пользователи. Интерфейс переведён на все 50 локализаций App Store Connect; для языков вне этого списка используется английский. Передавайте его, только если в вашем приложении есть собственный переключатель языка, — тогда отправляйте выбранный пользователем язык вместе с forceLocale=1, чтобы он имел приоритет над настройкой устройства. Никогда не зашивайте значение жёстко: именно из-за жёстко заданного zh-Hans японское приложение может показать китайский виджет.
  • platform (настоятельно рекомендуется) — какой сборкой пользуется посетитель. От него зависит, какие справочные статьи посетитель видит и отвечает ли ИИ по правилам App Store. Если его не передать, платформа определяется по User-Agent WebView: так распознаются iPhone, Android и Windows, но нельзя отличить сборку для Mac App Store от DMG-сборки и нельзя распознать iPad в режиме настольной версии. Значения: ios (iPhone и iPad), macos_appstore (сборка для Mac App Store), macos (DMG-сборка для прямой загрузки), android, windows, web.
  • externalId — уникальный идентификатор пользователя в вашем приложении. С ним оператор узнаёт пользователя, а история его диалогов объединяется.
  • email, name, avatarUrl — данные профиля, которые видят операторы.
  • hmac — подпись для проверки личности (см. ниже). Необязательно.

Скрытие контента по платформам (соответствие правилам App Store)

Правило App Store 3.1.1 не разрешает iOS-приложению показывать контент о внешних покупках, подписках или реферальных программах. Поэтому у каждой справочной статьи и каждого частого вопроса есть настройка видимости по платформам: откройте в панели управления страницу центра помощи или частых вопросов и переключите метки платформ у нужного элемента. Посетители на выбранной платформе перестают видеть его в списке и не могут открыть по прямой ссылке.

ИИ не отвечает по справочным статьям и частым вопросам — он отвечает по тому, что вы добавили в разделе «Training». У файлов и пар «вопрос — ответ» там такие же метки платформ, и элемент, скрытый для платформы, не используется в ответах посетителям на этой платформе. Если ИИ тоже не должен о нём упоминать, скройте его в обоих местах.

Надёжно ли это сработает, зависит от двух вещей. Во-первых, приложению стоит передавать platform самому — определение по User-Agent лишь запасной вариант. Во-вторых, значение должно отражать сборку, а не только операционную систему:

  • iPad не выделяется отдельно. На нём работает то же iOS-приложение по тем же правилам, поэтому ios охватывает и iPhone, и iPad.
  • У Mac две сборки. Сборка для Mac App Store подчиняется правилу 3.1.1, а DMG, который вы раздаёте со своего сайта, — нет. Операционная система у них одна, так что во время выполнения их не различить: решайте это на этапе сборки (например, флагом Xcode) и передавайте соответственно macos_appstore или macos. Ошибётесь — и либо сборка для магазина покажет контент о подписке во время ревью, либо DMG-сборка без всякой причины скроет половину центра помощи.

Изменения применяются сразу: настройка хранится в панели управления, а не в приложении, поэтому, чтобы изменить видимость, выпускать новую версию не нужно.

Проверка личности (HMAC, необязательно)

Чтобы доказать, что externalId действительно принадлежит вашему авторизованному пользователю (и защититься от подмены), передайте подпись HMAC. Вычисляйте её на своём сервере — никогда не кладите секретный ключ в приложение.

  • hmac = HMAC-SHA256(secretKey, externalId), результат — hex в нижнем регистре.
  • Подписываются данные externalId, поэтому всегда передавайте его: без него виджет использует собственный анонимный идентификатор, а подпись только по email отклоняется.
  • secretKey — секретный ключ ваших входящих (у каждых входящих свой, его можно взять в их настройках). Без hmac посетитель считается неподтверждённым.
import { createHmac } from 'node:crypto';

// SECRET_KEY lives only on your server, never ship it inside the app.
const hmac = createHmac('sha256', SECRET_KEY)
  .update(externalId)   // exactly the externalId you pass to the widget
  .digest('hex');

// Return { externalId, email, hmac } to the app, which appends them to the URL.

Особенности платформ

  • Android: сохраните разрешение INTERNET; перехватывайте кнопку «Назад», чтобы сначала выполнялся переход назад внутри WebView. Свежие версии webview_flutter поддерживают <input type="file"> для вложений.
  • iOS: HTTPS работает без исключений ATS. Загрузка изображений встроена в виджет: WKWebView сам открывает системный выбор фото, так что разрабатывать ничего не нужно. Для выбора из медиатеки разрешения не требуются. Ключ NSCameraUsageDescription в Info.plist нужен, только если вы хотите вариант «Снять фото» с камерой: без него приложение упадёт, когда пользователь выберет этот пункт.

Нужен нативный интерфейс?

Если вместо WebView вам нужен собственный нативный интерфейс чата, используйте клиенты, содержащие только логику, со страницы Мобильные SDK. Для большинства приложений описанный выше WebView быстрее выпустить, и он всегда поддерживает все функции.