Поведаю вам историю в двух частях о том, как мы боролись с модерацией Apple на протяжении месяца, победили их, но всё равно решили играть по их правилам. В первой части я дам вам предысторию и способ внедрения On-Demand Resources. Во второй статье поговорим о том, как эти ресурсы связать с WKWebView и не сойти с ума.
Если ваше приложение использует WKWebView для большего, нежели открытие ссылок, то рано или поздно вы можете столкнуться с реджектом по причине Guideline 4.7 - Design - HTML5 Games, Bots, etc. Это настигло и нас в приложении Zaruba.
Zaruba – это мобильное приложение с голосовым чатом и HTML5 играми, реализованными внутри WKWebView через script bridging. Все игры грузились по прямой https ссылке и взаимодействовали с нативным кодом через WKScriptMessageHandler. Несколько месяцев приложение успешно проходило модерацию, но в один момент мы словили реджект:
Guideline 4.7 - Design - HTML5 Games, Bots, etc.
We noticed that your app includes code that is not embedded in the binary, but the software does not fulfill all of the requirements outlined in Guideline 4.7 of the App Store Review Guidelines or you have not provided us with an index of the software available in your app.
It would be appropriate to remove any and all script bridging support for remote content associated with the games, including [GameOnReadyHandler userContentController:didReceiveScriptMessage:] and [GameUpdateStateHandler userContentController:didReceiveScriptMessage:].
После нескольких попыток прохождения модерации с надеждой авось прокатит как раньше, мы угодили на pull-review и теперь каждую нашу сборку проверяли разработчики из-за чего время прохождения ревью увеличилось до 5-8 дней.
Long story short, Apple не понравилось, что мы используем UIWebView-мост для общения с играми, которые не встроены в бинарники приложения, а значит Apple про них ничего не знает. Правда их всё равно можно переиграть, но об этом далее.
По пути наименьшего сопротивления
Изначальное переходить на On-Demand Resources не хотелось из-за отсутствия сколь бы то ни было подробной информации о внедрении этого инструмента и, как следствие, временными затратами.
Мы выдвинули теорию, что можно попытаться скрыть использование хэндлеров GameOnReadyHandler и GameUpdateStateHandler, объединив их в один хэндлер на свичах. Так мы создали GameEventsHandler.
enum GameEvent: String, CaseIterable {
case start = "init"
...
}
protocol GameEventsHandlerDelegate: AnyObject {
func didStart()
...
}
final class GameEventsHandler: NSObject, WKScriptMessageHandler {
weak var delegate: GameEventsHandlerDelegate?
var name: String { "init" }
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
guard let event = GameEvent(rawValue: message.name) else { return }
let messageBody = message.body as? String
let data = messageBody?.data(using: .utf8)
let body = try? JSONSerialization.jsonObject(with: data ?? Data(), options: .allowFragments) as? [String:Any]
switch event {
case .start:
delegate?.didStart()
...
}
}
}
Создали enum со всеми необходимыми типами и циклом добавляли хэндлеры в UIWebView.
private func setupHandlers() {
let handler = GameEventsHandler()
handler.delegate = self
GameEvent.allCases.forEach { event in
view?.addWebViewHandler(handler, name: event.rawValue)
}
}
К этому моменту прошло уже около двух недель с начала войны с Apple. Сроки горят, остальные фичи приложения уже давно должны быть в релизе. Мы с надеждой отправляем эту реализацию на проверку и... О чудо! Проверка пройдена спустя неделю, Apple сжалился. На этом можно было и закончить, если бы не риски повторения проблемы.
Играем по правилам
Что это вообще за зверь? Apple следующее даёт определение:
On-demand resources are app contents that are hosted on the App Store and are separate from the related app bundle that you download. They enable smaller app bundles, faster downloads, and richer app content. The app requests sets of on-demand resources, and the operating system manages downloading and storage. The app uses the resources and then releases the request. After downloading, the resources may stay on the device through multiple launch cycles, making access even faster.
Перевод
Ресурсы по требованию – это содержимое приложений, размещённое в App Store отдельно от соответствующего пакета приложений, который вы загружаете. Они позволяют использовать меньшие пакеты приложений, ускоряют загрузку и делают содержимое приложений более насыщенным. Приложение запрашивает наборы ресурсов по требованию, а операционная система управляет загрузкой и хранением. Приложение использует ресурсы, а затем отпускает запрос. После загрузки ресурсы могут оставаться на устройстве в течение нескольких циклов запуска, что делает доступ к ним ещё быстрее.
Понятно. А как пользоваться? В гугле можно найти примеры использования ODR в контексте игр, но нам нужно больше. Появился ряд вопросов:
Как упаковать наши HTML5 игры?
В какой момент подтягивать ресурсы и как лучше синхронизировать URL и tag?
Как решать проблему инвалидации кэша / версионирования?
Необходимо ли использовать свою URL-схему?
Как это всё красиво обернуть, чтобы было удобно пользоваться?
После углубленного изучения ODR и общения с разработчиками из VK Mini Apps прояснились многие моменты.
Новая версия игры == новая версия приложения. Отдельно подтянуть новую версию html можно только по https в обход правил Apple. Перекладываем ответственность на бэкенд. Пусть они при необходимости присылают https://, вместо odr:// в ссылках на игру.
Подменять теги нельзя. Как пошло в релиз, так и будет. Выкачиваем игры по тегам.
Самый большой подводный камень – CORS. Лучше использовать WKURLSchemeHandler, а не файл-схему.
Упаковываем игры
Все наши игры должны иметь унифицированную точку входу, например, index.html. Ставим задачу игровым фронтам, подключаем сборку игр к CI и оформляем как отдельный Pod для пущего удобства. Podspec выглядит стандартно, за исключением раздела с ODR.
Pod::Spec.new do |s|
...
s.subspec 'ODR' do |cs|
cs.on_demand_resources = {
'football' => ['Games/football/*'],
...
}
end
...
end
Отлично, переложили ответственность по упаковке игр на фронт и CI. Можно пользоваться.
Во второй части мы расскажем как писали свой Web-bridge c поддержкой кастомных URL-схем, логгером и навороченными хэндлерами ивентов. До встречи!
По всем вопросам пишите: t.me/zloysergunya или t.me/propertyWrapper