Site icon AppTractor

Проверяем, есть ли новая версия приложения в App Store

Как разработчики, когда мы выпускаем новую версию нашего приложения с новыми функциями и исправлениями ошибок, мы хотим, чтобы наши пользователи как можно скорее обновились до последней версии.

Однако у многих пользователей на устройствах не включены автоматические обновления, и если они не открывают сам App Store и не ищут доступные обновления, они могут так и не узнать, что вышла новая версия вашего приложения.

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

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

Используя iTunes Lookup

У Apple есть специальная конечная точка, которую можно использовать для проверки информации о приложении в App Store по идентификатору: https://itunes.apple.com/lookup?id={id} или по идентификатору бандла: https://itunes.apple.com/lookup?bundleId={bundleId}.

Конечная точка возвращает JSON-объект со списком результатов, соответствующих указанному идентификатору, и каждый из результатов содержит информацию о приложении, включая номер версии и минимальную версию ОС, необходимую для запуска приложения:

import Foundation

struct LookUpResponse: Decodable {
    let results: [LookUpResult]
    
    struct LookUpResult: Decodable {
        let version: String
        let minimumOsVersion: String
        let trackViewUrl: URL
    }
}

struct LatestAppStoreVersion {
    let version: String
    let minimumOsVersion: String
    let upgradeURL: URL
}

final class LookUpAPI {
    private let session: URLSession
    private let jsonDecoder: JSONDecoder
    
    init(session: URLSession = .shared, jsonDecoder: JSONDecoder = .init()) {
        self.session = session
        self.jsonDecoder = jsonDecoder
    }
    
    func getLatestAvailableVersion(for appID: String) async throws -> LatestAppStoreVersion? {
        let url = URL(string: "https://itunes.apple.com/lookup?appId=\(appID)")!
        let request = URLRequest(url: url)
        let (data, _) = try await session.data(for: request)
        let response = try jsonDecoder.decode(LookUpResponse.self, from: data)
        
        print(response)
    
        return response.results.first.map {
            .init(version: $0.version,
                  minimumOsVersion: $0.minimumOsVersion,
                  upgradeURL: $0.trackViewUrl)
        }
    }
}

Хотя этот подход прост в реализации, поскольку запрос не требует аутентификации, у него есть довольно серьезное ограничение: он возвращает только один результат для каждого идентификатора приложения, что означает, что если для вашего приложения доступно несколько платформ (iOS, macOS, watchOS и т.д.), вы получите информацию только для одной из них.

Этот подход хорошо подошел нам, поскольку Helm доступен только на macOS, но он не сработает, если у вас есть приложение, которое доступно на нескольких платформах.

Используя App Store Connect API

Более надежный подход, хотя и более сложный, заключается в использовании App Store Connect API для получения всех доступных версий, готовых к распространению для каждой платформы вашего приложения.

К сожалению, App Store Connect API требует аутентификации всех запросов с помощью JWT-токена, который вы должны сгенерировать, используя ключ API, что значительно увеличивает сложность.

Процесс генерации JWT не так прост, поэтому я бы посоветовал вам использовать библиотеку вроде appstoreconnect-swift-sdk, которая сделает это за вас.

После того как аутентификация будет выполнена, вам останется только сделать запрос к конечной точке https://api.appstoreconnect.apple.com/v1/apps/{id}/appStoreVersions с нужными параметрами и фильтрами и сопоставить полученный ответ с вашей моделью:

import Foundation
import AppStoreConnect_Swift_SDK

struct LatestAppStoreVersion {
    let version: String
    let minimumOsVersion: String
    let upgradeURL: URL
}

final class LatestVersionAPI {
    let provider: APIProvider
    
    init?() {
        guard let configuration = try? APIConfiguration(
            issuerID: "🙈",
            privateKeyID: "🙈",
            privateKey: "🙈") else {
            return nil
        }
        self.provider = APIProvider(configuration: configuration)
    }
    
    func getLatestAvailableVersion(for appID: String, platform: Platform) async throws -> LatestAppStoreVersion? {
        let filterPlatform: APIEndpoint.V1.Apps.WithID.AppStoreVersions.GetParameters.FilterPlatform = {
            switch platform {
            case .ios: return .ios
            case .macOs: return .macOs
            case .tvOs: return .tvOs
            case .visionOs: return .visionOs
            }
        }()
        
        let versionsRequest = APIEndpoint
            .v1
            .apps
            .id(appID)
            .appStoreVersions
            .get(parameters: .init(filterAppVersionState: [.readyForDistribution],
                                   filterPlatform: [filterPlatform],
                                   fieldsAppStoreVersions: [.versionString, .platform],
                                   fieldsBuilds: [.minOsVersion],
                                   limit: 1,
                                   include: [.build]))

        let versionsResponse = try await provider.request(versionsRequest)
        
        let minimumOsVersion: String? = versionsResponse
            .included?
            .compactMap { item in
                if case let .build(build) = item {
                    return build.attributes?.minOsVersion
                }
                
                return nil
            }
            .first
        
        guard let versionString = versionsResponse.data.first?.attributes?.versionString,
              let minimumOsVersion else {
            return nil
        }
        
        return LatestAppStoreVersion(version: versionString,
                                     minimumOsVersion: minimumOsVersion,
                                     upgradeURL: URL(string: "https://itunes.apple.com/app/id\(appID)")!)
    }
}

// Usage
let api = LatestVersionAPI()
let available = try await api?.getLatestAvailableVersion(
    for: "1596487035", 
    platform: .visionOs
)

Сравнение локальной и удаленной версий

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

extension LatestAppStoreVersion {
    var shouldUpdate: Bool {
        guard let currentVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String else {
            return false
        }
        let systemVersion = ProcessInfo().operatingSystemVersion
        let versionString = "\(systemVersion.majorVersion).\(systemVersion.minorVersion).\(systemVersion.patchVersion)"
        
        let isRemoteVersionHigherThanLocal = currentVersion.compare(self.version, options: .numeric) == .orderedAscending
        let isSystemVersionAllowed = versionString.compare(self.minimumOsVersion, options: .numeric) == .orderedDescending
        
        return  isRemoteVersionHigherThanLocal && isSystemVersionAllowed
    }
}

Источник

Exit mobile version