Создаем свое AppStore для распространения по программе iOS Enterprise Distribution

в 3:28, , рубрики: appstore, B2B, ios app, mobile development, разработка под iOS, метки: , ,

В developer подписках Apple есть возможность разрабатывать приложения для внутрекорпоротивного использования. При этом приложения не проходят проверку со стороны Apple и не обязаны «светится» в AppStore. Всё выше сказанное относится и к Ad Hoc приложениям, они принципиально ничем не отличаются, разве что временем жизни после создания.

Для распространения приложения хотелось решить задачи:

  1. пользователь самостоятельно мог установить приложение на свое устройство
  2. только авторизированный пользователь мог устанавливать приложение
  3. простая публикация приложения на сервере


Опишу, как реализовывали и какие были трудности.

1.

С первым пунктом всё понятно, есть информация на эту тему. Т.е. создаем правильную ссылку на plist файл, а в нем должна быть волшебная ссылка на ipa архив приложения.

2.

Второй пункт казался не такой и сложной проблемой: создаем страницу аутентификации и авторизируем пользователя. Далее выдаем ему ссылку на установку/обновление приложения. PROFIT.

На деле оказалось всё не так просто. Сессия между Safari (я не говорю уже о других браузерах) и AppStore не шарится. Немного подумав можно и этот вопрос решить – сделать сессионные ключи и передавать их в параметрах ссылки. Просто делаем аналог куки, которые передаются GET'ом и имеют ограничения по времени жизни/количество раз использования. Далее меня ждал еще один маленький нюанс. По задумке ссылка на plist выглядела примерно так: httр://example.com/getapp?key=F00DBEE&type=plist, для получения самого приложения параметр type=ipa. Оказалось, что при обработке ссылок устройство учитывает только первый параметр, остальные попросту отбрасывает – обидно. Но где наше не пропадало – все интересующие данные можно и в один параметр сложить.

Стоит обратить внимание еще на одну мелочь – перед тем как получить приложение AppStore делает запрос на сервер методом HEAD, а только потом скачивает сам фал GET'ом. В связи с этой хитрой хитростью у меня сессия «протухала» тогда когда не надо. Да само приложение выплевывалось два раза, т.к. привык для контента использовать HEAD === GET.

3.

Уменьшение количества данных для публикации = уменьшение ошибок третьей стороны. В идеальном случае, что бы в обновлении фигурировал один файл приложения — ipa, но пользователю, зачастую, хочется знать, какие изменения были в приложение. Так что остановились на варианте ipa + описание изменений.

Для установки приложения нужен plist, в котором описаны предустановочные свойства. Этот XML файл выглядит абсолютно одинаково (с разницей в значениях параметров) для разных приложений.

пример plist

<?xml version="1.0" encoding="UTF-8"?>
 <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
 <plist version="1.0">
     <dict>
        <key>items</key>
        <array>
            <dict>
                <key>assets</key>
                <array>
                    <!-- обязательный. Ссылка на приложение -->
                    <dict>
                         <key>kind</key>
                         <string>software-package</string>
                         <key>url</key>
                         <string>__URL__</string>
                     </dict>
                     <!-- не обязательный, иконка, которая показывается
                     в момент скачивания приложения.
                     При отсутствии будет серый квадрат -->
                     <dict>
                         <key>kind</key>
                         <string>display-image</string>
                         <key>needs-shine</key>
                         <false/>
                         <key>url</key>
                         <string>__SMALL_IMAGE__</string>
                     </dict>
                 </array>
                 <key>metadata</key>
                 <dict>
                     <!-- обязательный. внутренний
                     идентификатор приложения -->
                     <key>bundle-identifier</key>
                     <string>__IDENTIFIER__</string>
                     <!-- обязательный. Версия сборки приложения -->
                     <key>bundle-version</key>
                     <string>__VERSION__</string>
                     <key>kind</key>
                     <string>software</string>
                     <!-- обязательный. Наименование приложения -->
                     <key>title</key>
                     <string>__TITLE__</string>
                 </dict>
             </dict>
         </array>
     </dict>
 </plist>

Данные параметры можно получить из другого plist, находящегося в недрах ipa-файла (который является zip архивом) приложения — Info.plist. Недостаток только в том, что он бинарный. В MacOS есть хорошая консольная утилита plutil для конвертации форматов plist'ов, но не у всех на серверах стоят MacOS.

Решить эту проблему можно путем написания своего конвертера, либо собрать нативное приложение, либо воспользоваться Perl скриптом, Windows можно поставить приложения от Apple и найти консольную утилиту по адресу «C:Program Files (x86)Common FilesAppleApple Application Support». Вариант со своей реализацией заманчив, но дорог. Я воспользовался Perl'ом. После конвертации бинарного plist получаем примерно такой XML:

пример bplist


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
   <plist version="1.0">
      <dict>
         <key>BuildMachineOSBuild</key>
         <string>14D73</string>
         <key>CFBundleDevelopmentRegion</key>
         <string>en</string>
         <key>CFBundleDisplayName</key>
         <string>MyApp</string>
         <key>CFBundleExecutable</key>
         <string>MyApp</string>
         <key>CFBundleIcons</key>
         <dict>
            <key>CFBundlePrimaryIcon</key>
            <dict>
               <key>CFBundleIconFiles</key>
               <array>
                  <string>i57.png</string>
                  <string>i114.png</string>
               </array>
            </dict></dict>
            <key>CFBundleIdentifier</key>
            <string>com.company.MyApp.ios</string>
            <key>CFBundleInfoDictionaryVersion</key>
            <string>6.0</string>
            <key>CFBundleName</key>
            <string>MyApp</string>
            <key>CFBundlePackageType</key>
            <string>APPL</string>
            <key>CFBundleShortVersionString</key>
            <string>1.0</string>
            <key>CFBundleSupportedPlatforms</key>
            <array>
               <string>iPhoneOS</string>
            </array>
            <key>CFBundleVersion</key>
            <string>1.0.3</string>
            <!--
               еще много ключей и их значений, который в данной статье не интересны
            -->
         </dict>
      </plist>

Из описания нам интересны: CFBundleVersion, CFBundleIdentifier, CFBundleDisplayName. Это наименование, версия, идентификатор приложения соответственно. Можно бонусом еще и иконку вытянуть. Для получения значений используем такой XPath /plist/dict/key[contains(text(),'__KEY__')]/following-sibling::string[1]/text() где __KEY__ необходимые ключи.

Таким образом можно распространять не только Enterprise, но и AdHoc без особых проблем.

Обновление приложения.

Следить за обновлением приложения должно само приложение, т.к. оно не является частью AppStore. Да и вообще за приложением b2b нужно следить очень внимательно, оно еще может не перенестись с одного устройства на другое при дампе через iTunes. Для непосредственного обновления приложения из самого приложения необходимо открывать в Safari волшебную ссылку на манифест (plist) файл, которая начинается так itms-services://?action=download-manifest&url=.

В качестве заключения.

В данной реализации, быть может, только один костыль — использование стороннего приложения для чтения info.plist, остальные «телодвижения» можно совершить без особых проблем на всех (большинстве) платформ/технологий и т.д.

Автор: wlw

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js