Реализация системы разрешений для мини-программ в Super App
Когда мини-программа внутри Super App хочет прочитать геолокацию или отправить уведомление — кто должен спрашивать у пользователя разрешение? Хост-приложение уже может иметь его. Мини-программа не должна иметь к нему прямого доступа. Между ними нужен permission broker.
Два слоя разрешений
Система разрешений для мини-программ — это не просто обёртка над системными ActivityCompat.requestPermissions. Здесь два независимых слоя:
Первый слой: платформенные разрешения — те же camera, location, contacts, что запрашивает любое Android/iOS приложение. Хост-приложение держит их у себя и делегирует мини-программе только то, что явно разрешено.
Второй слой: платформенные API разрешения — доступ к API самой Super App: хранилище профиля пользователя, история заказов, платёжные методы, контакты внутри экосистемы. Это полностью кастомный слой, системные разрешения здесь не помогают.
Манифест мини-программы
Каждая мини-программа поставляется с манифестом, где декларирует нужные permissions:
{
"miniAppId": "com.partner.food_delivery",
"version": "1.2.0",
"permissions": {
"system": ["LOCATION_FINE", "CAMERA"],
"platform": ["USER_PROFILE_READ", "PAYMENT_INITIATE", "ORDER_HISTORY_READ"]
},
"permissionRationale": {
"LOCATION_FINE": "Для расчёта адреса доставки",
"CAMERA": "Для сканирования QR-кодов меню"
}
}
При установке мини-программы пользователь видит список запрашиваемых разрешений — как при установке обычного Android-приложения. Разрешения, не задекларированные в манифесте, недоступны даже если хост их имеет.
Permission Broker на стороне хоста
Центральный компонент — broker, который проверяет все обращения к нативным API:
class MiniAppPermissionBroker(
private val permissionStore: MiniAppPermissionStore,
private val systemPermissionDelegate: SystemPermissionDelegate
) {
suspend fun requestPermission(
miniAppId: String,
permission: MiniAppPermission,
context: Activity
): PermissionResult {
// 1. Задекларирована ли в манифесте?
if (!manifestValidator.isDeclared(miniAppId, permission)) {
return PermissionResult.DENIED_NOT_DECLARED
}
// 2. Уже выдана?
val stored = permissionStore.getStatus(miniAppId, permission)
if (stored == PermissionStatus.GRANTED) return PermissionResult.GRANTED
if (stored == PermissionStatus.DENIED_PERMANENTLY) return PermissionResult.DENIED_PERMANENTLY
// 3. Для системных разрешений — проверяем хост, потом запрашиваем
if (permission.isSystemPermission()) {
val hostHas = systemPermissionDelegate.hasPermission(permission.androidName)
if (!hostHas) {
// Запрашиваем у пользователя от имени хоста
val result = systemPermissionDelegate.request(permission.androidName, context)
if (result != GRANTED) return PermissionResult.DENIED_BY_USER
}
}
// 4. Показываем платформенный диалог разрешения
val userDecision = showPermissionDialog(miniAppId, permission, context)
permissionStore.save(miniAppId, permission, userDecision)
return userDecision
}
}
Управление разрешениями пользователем
Пользователь должен иметь возможность отозвать любое разрешение в любой момент. В настройках Super App — экран с перечнем установленных мини-программ и их разрешений:
Мини-программа: "Доставка еды"
├── Геолокация (точная) ............. ВКЛ [переключатель]
├── Камера .......................... ВЫК [переключатель]
├── Профиль пользователя ............ ВКЛ [переключатель]
└── История заказов ................. ВКЛ [переключатель]
Отзыв разрешения работает сразу — без перезапуска мини-программы. Broker при следующем вызове API вернёт PERMISSION_REVOKED, и мини-программа должна корректно обработать эту ошибку.
Runtime проверка при каждом вызове API
Разрешения могут быть отозваны асинхронно — пока мини-программа работает. Поэтому каждый вызов платформенного API проходит через broker, а не только при инициализации:
// Вызов из JS-моста
@JavascriptInterface
fun getUserLocation(callbackId: String) {
val miniAppId = currentMiniAppContext.id
coroutineScope.launch {
when (permissionBroker.checkPermission(miniAppId, MiniAppPermission.LOCATION_FINE)) {
PermissionResult.GRANTED -> {
val location = locationProvider.getLastLocation()
bridge.sendSuccess(callbackId, location.toJson())
}
PermissionResult.DENIED_PERMANENTLY -> {
bridge.sendError(callbackId, "PERMISSION_DENIED_PERMANENTLY")
}
else -> {
bridge.sendError(callbackId, "PERMISSION_REQUIRED")
}
}
}
}
Аудит использования разрешений
Каждое обращение к чувствительному API логируется: timestamp, miniAppId, permission, был ли granted или denied. Это позволяет обнаружить мини-программу, которая запрашивает геолокацию каждые 5 секунд в фоне — и заблокировать её на платформе.
Сроки
Система разрешений с двумя слоями, UI настроек и audit trail: 2–3 дня при наличии готового permission store. Если разрабатывается с нуля включая манифест-валидатор и экраны управления — 4–6 дней.







