diff --git a/Sources/XKit/DeveloperServices/OpenAPI/DeveloperAPIPages.swift b/Sources/XKit/DeveloperServices/OpenAPI/DeveloperAPIPages.swift index 1964d15c..a56bfe87 100644 --- a/Sources/XKit/DeveloperServices/OpenAPI/DeveloperAPIPages.swift +++ b/Sources/XKit/DeveloperServices/OpenAPI/DeveloperAPIPages.swift @@ -25,8 +25,8 @@ public struct DeveloperAPIPages: AsyncSequence { public var getNext: @Sendable (Page) -> String? public init( + _ getNext: @escaping @Sendable (Page) -> String?, request: @escaping @Sendable () async throws -> Page, - next getNext: @escaping @Sendable (Page) -> String? ) { self.request = request self.getNext = getNext diff --git a/Sources/XKit/DeveloperServices/Profiles/DeveloperServicesFetchProfileOperation.swift b/Sources/XKit/DeveloperServices/Profiles/DeveloperServicesFetchProfileOperation.swift index c9ac8a3f..d1a8ebdb 100644 --- a/Sources/XKit/DeveloperServices/Profiles/DeveloperServicesFetchProfileOperation.swift +++ b/Sources/XKit/DeveloperServices/Profiles/DeveloperServicesFetchProfileOperation.swift @@ -49,32 +49,45 @@ public struct DeveloperServicesFetchProfileOperation: DeveloperServicesOperation throw Errors.tooManyMatchingBundleIDs } - let profiles = bundleID.relationships?.profiles?.data ?? [] - switch profiles.count { - case 0: - // we're good - break - case 1: - _ = try await context.developerAPIClient.profilesDeleteInstance(path: .init(id: profiles[0].id)).noContent - default: - // if the user has >1 profile, it's probably okay to add another one (acceptable for non-free accounts?) - break - } - - // Match locally rather than with filter[serialNumber]: the API lists serials with a - // leading zero nibble ("0E41…") but its filter only matches them without it ("E41…"). - let certs = try await DeveloperAPIPages { + // match locally rather than with filter[serialNumber]: server-side matching bugs out + // when the serial has leading zeroes (#305) + let certs = try await DeveloperAPIPages(\.links.next) { try await context.developerAPIClient.certificatesGetCollection().ok.body.json - } next: { - $0.links.next } .map(\.data) .reduce(into: [], +=) .filter { $0.attributes?.serialNumber.map(signingInfo.certificate.hasSerialNumber) == true } - let allDevices = try await context.developerAPIClient.devicesGetCollection() + let allDevices = try await DeveloperAPIPages(\.links.next) { + try await context.developerAPIClient.devicesGetCollection().ok.body.json + } + .map(\.data) + .reduce(into: [], +=) + .filter { + $0.attributes?.status?.value1 != .disabled + && $0.attributes?.deviceClass?.value1?.supportsIOSApps == true + } + + try Task.checkCancellation() + + let profileName = "XTL profile \(bundleID.id)" + + let profiles = bundleID.relationships?.profiles?.data ?? [] + if !profiles.isEmpty { + // we can't create multiple profiles with the same name, so + // if there's already one delete it first + let matchingProfiles = try await context.developerAPIClient.profilesGetCollection(query: .init( + filter_lbrack_name_rbrack_: [profileName], + filter_lbrack_id_rbrack_: profiles.map(\.id), + fields_lbrack_profiles_rbrack_: [.name], + )) .ok.body.json.data - .filter { $0.attributes?.deviceClass?.value1?.supportsIOSApps == true } + .filter { $0.attributes?.name == profileName } + + _ = try await matchingProfiles.concurrentMap { + try await context.developerAPIClient.profilesDeleteInstance(path: .init(id: $0.id)).noContent + } + } let response = try await context.developerAPIClient.profilesCreateInstance( body: .json( @@ -82,7 +95,7 @@ public struct DeveloperServicesFetchProfileOperation: DeveloperServicesOperation data: .init( _type: .profiles, attributes: .init( - name: "XTL profile \(bundleID.id)", + name: profileName, profileType: .init(.iosAppDevelopment) ), relationships: .init( diff --git a/Sources/XToolSupport/DSCommands/DSCertificatesCommand.swift b/Sources/XToolSupport/DSCommands/DSCertificatesCommand.swift index 9912caca..bfd442b0 100644 --- a/Sources/XToolSupport/DSCommands/DSCertificatesCommand.swift +++ b/Sources/XToolSupport/DSCommands/DSCertificatesCommand.swift @@ -25,10 +25,8 @@ struct DSCertificatesListCommand: AsyncParsableCommand { func run() async throws { let client = DeveloperAPIClient(auth: try AuthToken.saved().authData()) - let certificates = try await DeveloperAPIPages { + let certificates = try await DeveloperAPIPages(\.links.next) { try await client.certificatesGetCollection().ok.body.json - } next: { - $0.links.next } .map(\.data) .reduce(into: [], +=) diff --git a/Sources/XToolSupport/DSCommands/DSDevicesCommand.swift b/Sources/XToolSupport/DSCommands/DSDevicesCommand.swift index 86e51009..2583c4dc 100644 --- a/Sources/XToolSupport/DSCommands/DSDevicesCommand.swift +++ b/Sources/XToolSupport/DSCommands/DSDevicesCommand.swift @@ -24,10 +24,8 @@ struct DSDevicesListCommand: AsyncParsableCommand { func run() async throws { let client = DeveloperAPIClient(auth: try AuthToken.saved().authData()) - let devices = try await DeveloperAPIPages { + let devices = try await DeveloperAPIPages(\.links.next) { try await client.devicesGetCollection().ok.body.json - } next: { - $0.links.next } .map(\.data) .reduce(into: [], +=) diff --git a/Sources/XToolSupport/DSCommands/DSIdentifiersCommand.swift b/Sources/XToolSupport/DSCommands/DSIdentifiersCommand.swift index 5e2a4ab4..a17cf0fd 100644 --- a/Sources/XToolSupport/DSCommands/DSIdentifiersCommand.swift +++ b/Sources/XToolSupport/DSCommands/DSIdentifiersCommand.swift @@ -24,10 +24,8 @@ struct DSIdentifiersListCommand: AsyncParsableCommand { func run() async throws { let client = DeveloperAPIClient(auth: try AuthToken.saved().authData()) - let bundleIDs = try await DeveloperAPIPages { + let bundleIDs = try await DeveloperAPIPages(\.links.next) { try await client.bundleIdsGetCollection().ok.body.json - } next: { - $0.links.next } .map(\.data) .reduce(into: [], +=) diff --git a/Sources/XToolSupport/DSCommands/DSProfilesCommand.swift b/Sources/XToolSupport/DSCommands/DSProfilesCommand.swift index 69221ad7..01fe708f 100644 --- a/Sources/XToolSupport/DSCommands/DSProfilesCommand.swift +++ b/Sources/XToolSupport/DSCommands/DSProfilesCommand.swift @@ -27,10 +27,8 @@ struct DSProfilesListCommand: AsyncParsableCommand { func run() async throws { let client = DeveloperAPIClient(auth: try AuthToken.saved().authData()) - let profiles = try await DeveloperAPIPages { + let profiles = try await DeveloperAPIPages(\.links.next) { try await client.profilesGetCollection().ok.body.json - } next: { - $0.links.next } .map(\.data) .reduce(into: [], +=)