Перейти к основному содержимому
Версия: next

Sandbox мобильных SDK для команд приложений

Для команд iOS и Android заказчика. Sandbox — инсталляция Shtab Connect на тестовом стенде Shtab с доступом из интернета. На ней команда открывает чат из своей сборки приложения раньше, чем бэкенд заказчика научится выпускать chat token, и проверяет встраивание SDK. После установки в контур заказчика сборки переключаются на его инсталляцию сменой адреса, ключа размещения и выпуска chat token (ограничения).

Встраивание SDK описано в руководствах iOS и Android. Здесь — только то, что относится к sandbox.

1. Адреса и ключи​

Адрес инсталляции (installationURL / installationUrl)https://helpdesk-support.shtab.io
UI чата для WebViewhttps://helpdesk-support.shtab.io/widget/mobile/ (версия моста — …/widget/mobile/bridge.json)
Размещение вашего приложенияpk_sbx_sandbox_app. Bundle ID и applicationId вашего приложения вносит Shtab по заявке
Размещение sample-приложений Shtabpk_sbx_sandbox_sample (bundle id и applicationId ru.shtab.helpdesk.sample)
Издатель chat tokeniss = https://helpdesk-support.shtab.io/sandbox, алгоритм HS256, секрет выдаёт Shtab (доступ)
Гостевой чат в браузереhttps://helpdesk-playground.shtab.io/?tenant=sandbox — «сайт» площадки с виджетом sandbox
Ответы поддержкиочередь «Sandbox», оператор sandbox-operator в рабочем месте https://helpdesk.shtab.io

Ключ размещения публичен: он попадает в приложение. Чужие данные по нему не открываются: в размещение авторизованной зоны входят только по chat token, подписанному секретом sandbox.

Sandbox — отдельная организация sandbox на стенде. Размещения, издатель, оператор и обращения принадлежат только ей. Chat token проверяется только издателями организации того размещения, для которого он выпущен (aud). Поэтому секрет sandbox не даёт доступа к другим организациям стенда.

2. Как получить доступ​

  • Секрет chat token (не короче 32 байт) Shtab передаёт команде вне репозиториев, задач и открытой почты, например через менеджер паролей или зашифрованный канал. Срок доступа указан в сообщении с ним. Не кладите секрет в репозиторий, в код или ресурсы сборки, в логи и CI без маскирования. Узнав об утечке, сразу сообщите Shtab: секрет сменят.
  • Bundle ID и applicationId вашего приложения пришлите Shtab вместе с заявкой: их вносят в размещение pk_sbx_sandbox_app. Для чата они не нужны, без них не работает только push (push).
  • Рабочее место оператора. Учётную запись sandbox-operator (видит только sandbox) Shtab выдаёт по заявке. Иначе на тестовые сообщения отвечает Shtab.

3. Выпуск chat token​

В продукте chat token подписывает бэкенд вашего приложения для вошедшего пользователя. В sandbox его подписывает ваш тестовый бэкенд секретом sandbox. Для быстрой проверки можно подписать токен и вручную (curl ниже) или в отладочной сборке (Swift и Kotlin ниже).

Заголовок — {"alg":"HS256","typ":"JWT"}, подпись — HMAC-SHA256 секретом sandbox. Поля:

ПолеЗначение
isshttps://helpdesk-support.shtab.io/sandbox — ровно так
audключ размещения, в котором откроется чат: pk_sbx_sandbox_app (или pk_sbx_sandbox_sample для sample)
subпостоянный ID пользователя в вашей системе: по нему находятся его обращения. В sandbox — только тестовые ID, не номера телефонов, не e-mail и не ID реальных клиентов
nameимя, которое увидит оператор (необязательно). Только вымышленное
iat, expвремя выпуска и окончания (Unix-секунды); exp − iat не больше 600 с, рекомендуем 300 с
jtiуникальная строка (UUID): токен действует один раз

Остальные поля sandbox не сохраняет. SDK просит у провайдера новый токен при каждом открытии чата и когда сессия чата кончилась, поэтому кешировать токен в приложении не нужно. Отказ при обмене токена на сессию (POST /public/api/v1/session/token → 401) означает неверную подпись, iss или aud, истёкший или повторно использованный токен.

curl (bash, openssl; на macOS и Linux):

read -rs SECRET # секрет sandbox: вставить и нажать Enter, в историю не попадает
PLACEMENT=pk_sbx_sandbox_app
b64url() { openssl base64 -A | tr '+/' '-_' | tr -d '='; }
now=$(date +%s)
header=$(printf '{"alg":"HS256","typ":"JWT"}' | b64url)
payload=$(printf '{"iss":"https://helpdesk-support.shtab.io/sandbox","aud":"%s","sub":"tester-1","name":"Тестировщик","iat":%d,"exp":%d,"jti":"%s"}' \
"$PLACEMENT" "$now" "$((now + 300))" "$(uuidgen)" | b64url)
signature=$(printf '%s.%s' "$header" "$payload" | openssl dgst -sha256 -hmac "$SECRET" -binary | b64url)
TOKEN="$header.$payload.$signature"

# Проверка: обмен на сессию чата (200 — токен принят; второй обмен того же токена — 401)
curl -sS https://helpdesk-support.shtab.io/public/api/v1/session/token \
-H 'Content-Type: application/json' \
-d "{\"placement_key\":\"$PLACEMENT\",\"chat_token\":\"$TOKEN\"}"

Kotlin (JVM-бэкенд или отладочная сборка; только JDK):

import java.util.Base64
import java.util.UUID
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec

/** userId и name — без кавычек и обратной косой черты; в настоящем коде JSON собирает библиотека. */
fun sandboxChatToken(secret: String, placementKey: String, userId: String, name: String): String {
val b64 = Base64.getUrlEncoder().withoutPadding()
val now = System.currentTimeMillis() / 1000
val header = b64.encodeToString("""{"alg":"HS256","typ":"JWT"}""".toByteArray())
val claims = """{"iss":"https://helpdesk-support.shtab.io/sandbox","aud":"$placementKey",""" +
""""sub":"$userId","name":"$name","iat":$now,"exp":${now + 300},"jti":"${UUID.randomUUID()}"}"""
val signed = "$header.${b64.encodeToString(claims.toByteArray())}"
val mac = Mac.getInstance("HmacSHA256").apply { init(SecretKeySpec(secret.toByteArray(), "HmacSHA256")) }
return "$signed.${b64.encodeToString(mac.doFinal(signed.toByteArray()))}"
}

Swift (CryptoKit, отладочная сборка):

import CryptoKit
import Foundation

func sandboxChatToken(secret: String, placementKey: String, userID: String, name: String) throws -> String {
func b64url(_ data: Data) -> String {
data.base64EncodedString()
.replacingOccurrences(of: "+", with: "-")
.replacingOccurrences(of: "/", with: "_")
.replacingOccurrences(of: "=", with: "")
}
let now = Int(Date().timeIntervalSince1970)
let claims: [String: Any] = [
"iss": "https://helpdesk-support.shtab.io/sandbox", "aud": placementKey,
"sub": userID, "name": name, "iat": now, "exp": now + 300, "jti": UUID().uuidString,
]
let header = b64url(try JSONSerialization.data(withJSONObject: ["alg": "HS256", "typ": "JWT"]))
let signed = "\(header).\(b64url(try JSONSerialization.data(withJSONObject: claims)))"
let mac = HMAC<SHA256>.authenticationCode(for: Data(signed.utf8), using: SymmetricKey(data: Data(secret.utf8)))
return "\(signed).\(b64url(Data(mac)))"
}

Подписывать токен в приложении можно только в отладочной сборке и только для sandbox. Секрет вводится на устройстве вручную и не хранится в коде. В релизной сборке и в контуре заказчика chat token выпускает только бэкенд.

Sample-приложения Shtab берут токен у сервиса «Выдача chat token»: POST {sub, placement, name} → {token}. Для sandbox такой сервис запускается на своей машине (Python 3, без зависимостей). Адреса по умолчанию у sample уже нужные: http://localhost:5175/lk/token на симуляторе iOS и http://10.0.2.2:5175/lk/token на эмуляторе Android.

# sandbox_tokens.py: HELPDESK_SANDBOX_CHAT_TOKEN_SECRET=… python3 sandbox_tokens.py (только на своей машине)
import base64, hashlib, hmac, json, os, time, uuid
from http.server import BaseHTTPRequestHandler, HTTPServer

SECRET = os.environ['HELPDESK_SANDBOX_CHAT_TOKEN_SECRET'].encode()
ISSUER = 'https://helpdesk-support.shtab.io/sandbox'


def b64url(raw: bytes) -> str:
return base64.urlsafe_b64encode(raw).rstrip(b'=').decode()


def chat_token(sub: str, placement: str, name: str) -> str:
now = int(time.time())
claims = {'iss': ISSUER, 'aud': placement, 'sub': sub, 'name': name,
'iat': now, 'exp': now + 300, 'jti': str(uuid.uuid4())}
head = b64url(json.dumps({'alg': 'HS256', 'typ': 'JWT'}).encode())
signed = f'{head}.{b64url(json.dumps(claims).encode())}'
return f'{signed}.{b64url(hmac.new(SECRET, signed.encode(), hashlib.sha256).digest())}'


class Tokens(BaseHTTPRequestHandler):
def do_POST(self) -> None:
login = json.loads(self.rfile.read(int(self.headers['Content-Length'])))
token = chat_token(login['sub'], login['placement'], login.get('name', ''))
self.send_response(200)
self.send_header('Content-Type', 'application/json')
self.end_headers()
self.wfile.write(json.dumps({'token': token}).encode())


HTTPServer(('127.0.0.1', int(os.environ.get('PORT', '5175'))), Tokens).serve_forever()

В sample на экране стенда укажите адрес https://helpdesk-support.shtab.io и ключ pk_sbx_sandbox_sample, затем войдите как «Анна» или «Борис».

4. Подключение SDK​

Версия SDK — 1.0.0-beta.2. Код тот же, что в руководствах (iOS, Android), меняются только адрес и ключ. Провайдер chat token обращается к вашему тестовому бэкенду.

try HelpdeskSDK.configure(
installationURL: URL(string: "https://helpdesk-support.shtab.io")!,
placementKey: "pk_sbx_sandbox_app"
)
HelpdeskSDK.setChatTokenProvider { try? await testBackend.sandboxChatToken() } // nil — нет пользователя
HelpdeskSdk.configure(this, installationUrl = "https://helpdesk-support.shtab.io", placementKey = "pk_sbx_sandbox_app")
HelpdeskSdk.setChatTokenProvider { callback ->
testBackend.sandboxChatToken { token -> callback.onChatToken(token) } // null — нет пользователя
}
  • Адрес — только https://, без пути. Исключения ATS (iOS) и cleartextTrafficPermitted (Android) для sandbox не нужны.
  • Пины сертификата (certificatePins) на sandbox не включайте: сертификат стенда может смениться без предупреждения. Pinning проверяется на инсталляции заказчика с его ключами.
  • Смена пользователя: logout() при выходе. Следующий пользователь входит своим токеном с другим sub (iOS, Android).
  • Если в приложении задан WKAppBoundDomains (iOS), добавьте в список helpdesk-support.shtab.io.

5. Push​

На sandbox push проверяется только на sample-приложениях Shtab, с ключами APNs и Firebase Shtab. Ключи заказчика на стенд Shtab не попадают. Поэтому для sandbox команде не нужно присылать ключ APNs или JSON сервисного аккаунта Firebase.

Что команда проверяет сама, без сервера:

  • регистрацию push-токена: registerPushToken(…). Сервер принимает токен устройства и на sandbox, но в размещении pk_sbx_sandbox_app push не отправляется;
  • обработку уведомления и переход в обращение после повторного входа — debug-хелпером SDK: HelpdeskDebug.testPushUserInfo / scheduleTestNotification (iOS) и HelpdeskDebug.testPushData / testNotificationIntent (Android). Он даёт тот же payload, что шлёт сервер (iOS, Android).

Настоящий push в приложении команды работает в контуре заказчика. Что для этого нужно:

  • iOS: Bundle ID; ключ APNs (файл AuthKey_<KeyID>.p8), Key ID и Team ID; окружение (sandbox для отладочных сборок из Xcode, production для TestFlight и App Store); capability Push Notifications в приложении.
  • Android: applicationId; проект Firebase с включённым FCM API (V1); JSON сервисного аккаунта с ролью Firebase Cloud Messaging API Admin; id канала уведомлений, если он не shtab_helpdesk из SDK.

Ключи вносит администратор заказчика в «Интеграции → Мобильные приложения» своей инсталляции. Shtab ключи не получает.

6. Ограничения​

  • Только тестовые данные. Никаких реальных клиентов, персональных данных и данных production: sub — тестовые ID, name — вымышленные имена, во вложениях — тестовые файлы. Sandbox не сохраняет дополнительные поля токена (e-mail, телефон).
  • Данные sandbox удаляются. Shtab может очистить обращения и пересоздать sandbox без предупреждения.
  • Стенд, а не production. Стенд часто обновляется и может быть недоступен несколько минут. SLA нет.
  • Лимиты public API (как в продукте): 120 запросов в минуту с одного IP-адреса (IPv6 — с сети /64), 60 в минуту на сессию чата, 3000 в минуту на размещение, 10 000 в минуту на организацию. Сверх лимита — 429 с Retry-After. Нагрузочные тесты на sandbox не проводятся. chat token живёт не дольше 10 минут и действует один раз.
  • Переход в контур заказчика: адрес инсталляции заказчика, ключ размещения из его «Мобильных приложений», chat token выпускает бэкенд заказчика издателем, которого завёл администратор организации (RS256/ES256 или HS256). Секрет sandbox там не действует.

7. Контакты​

Вопросы по sandbox, заявки (bundle id, applicationId, новый секрет) и сообщения об утечке секрета — ответственному Shtab, который выдал доступ. Его имя и канал указаны в сообщении с доступом. Ошибки SDK присылайте с кодами из logger (диагностика), временем и ID пользователя (sub), без токенов и секретов.