Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Fastest trip to AppStore Connect

giginet
September 18, 2018

Fastest trip to AppStore Connect

giginet

September 18, 2018
Tweet

More Decks by giginet

Other Decks in Programming

Transcript

  1. • ৄղFastfile • iOSDC 2018 2೔໨ϨΪϡϥʔτʔΫ • https://speakerdeck.com/giginet/xiang-jie-fastfile • •

    iOSDC 2018 3೔໨LT • https://speakerdeck.com/giginet/-15 • ୀ۶ͳ͜ͱ͸AppStore Connect APIʹ΍ΒͤΑ͏ • iOSDC Reject Conference 2018
  2. • What's New in App Store Connect • https://developer.apple.com/videos/play/ wwdc2018/301/

    • Automating App Store Connect • https://developer.apple.com/videos/play/ wwdc2018/303/
  3. New Feature Summary App Store Connect API Unified user management

    App delivery on Linux TestFlight public links Sales and Trends overview App Store Connect for iOS Free trials for paid apps NEW
  4. Lifecycle Manage Users Design and Develop Deliver Provision Analyze Prepare

    App Store Metadata Distribute App Store Connect API Beta Test
  5. apps betaLicenseAgreements builds buildBetaDetails betaAppReviewSubmissions betaAppLocalizations betaBuildLocalizations betaAppReviewDetails appEncryptionDeclarations userInvitations

    bundleIds devices certificates salesReports financeReports betaTesterInvitations buildDeliveries betaGroups users profiles betaTesters
  6. > api.appstoreconnect.apple.com/v1/users/17cbd794-94a3-c7b0-1051 > GET HTTP/1.1 200 OK { "data": {

    "type": "users", "id": "17cbd794-94a3-c7b0-1051", "attributes": { "firstName": "Kate", "lastName": "Bell", "email": "[email protected]", … }, "relationships": {…}, "links": { "self": "…/v1/users/17cbd794-94a3-c7b0-1051" } } ]
  7. { "data": { "type": "userInvitations", "attributes": { "firstName": "John", "lastName":

    "Appleseed", "email": "[email protected]", "roles": ["DEVELOPER"], "allAppsVisible": true } } } > > POST /v1/userInvitations
  8. { "data": { "type": "users", "id": "24e811a2-2ad0-46e4-b632", "attributes": { "roles":

    ["DEVELOPER", "MARKETING"] } } } > > PATCH /v1/users/24e811a2-2ad0-46e4-b632
  9. // swift-tools-version:4.2 // The swift-tools-version declares the minimum version of

    Swift required to build this package. import PackageDescription let package = Package( name: "MyExecutable", dependencies: [ .package(url: "https://github.com/giginet/ Wormhole.git", .from("0.1.0")), ], targets: [ .target( name: "MyExecutable", dependencies: ["Wormhole"]), ] ) EFQFOEFODZ௥Ճ
  10. import Foundation import Wormhole import Result let client = try!

    Client( p8Path: URL(fileURLWithPath: "key.p8"), issuerID: UUID(uuidString: "b91d85c7- b7db-4451-8f3f-9a3c8af9a392")!, keyID: “100000" ) *TTVFS*% ,FZ*% 1SJWBUF,FZ͕ඞཁ
  11. HTTP/1.1 200 OK { "data": [{ "type": "users", "id": "17cbd794-94a3-c7b0-1051",

    "attributes": { "firstName": "Kate", "lastName": "Bell", "email": "[email protected]", … }, "relationships": {…}, "links": { "self": "https://api.appstoreconnect.apple.com/v1/users/17cbd794-94a3-c7b0-1 } }, { api.appstoreconnect.apple.com/v1/users/17cbd794-94a3-c7b0-1 > GET api.appstoreconnect.apple.com/v1/users
  12. enum Role: String, Codable { case developer = "DEVELOPER" case

    marketing = "MARKETING" } struct User: AttributeType { let firstName: String let lastName: String let email: String let roles: [Role] }
  13. HTTP/1.1 200 OK { "data": [{ "type": "users", "id": "17cbd794-94a3-c7b0-1051",

    "attributes": { "firstName": "Kate", "lastName": "Bell", "email": "[email protected]", … }, "relationships": {…}, "links": { "self": "https://api.appstoreconnect.apple.com/v1/users/17cbd794-94a3-c7b0-1 } }, { api.appstoreconnect.apple.com/v1/users/17cbd794-94a3-c7b0-1 > GET api.appstoreconnect.apple.com/v1/users
  14. struct GetUsersRequest: RequestType { typealias Response = CollectionContainer<User> let method:

    HTTPMethod = .get let path = "/users" let payload: RequestPayload = .void } ෳ਺ͷ݁ՌΛड͚औΔܕ 3FRVFTUCPEZ͸ۭ
  15. let request = GetUsersRequest() client.send(request) { (result: Result<CollectionContainer<User>, ClientError>) in

    switch result { case .success(let container): let firstUser: User = container.data.first! print("Name: \(firstUser.firstName) \ (firstUser.lastName)") print("Email: \(firstUser.email)") print("Roles: \(firstUser.roles)") case .failure(let error): print("Something went wrong") print(String(describing: error)) } } ϢʔβʔͷҰཡ͕औΕΔ
  16. { "data": { "type": "users", "id": "24e811a2-2ad0-46e4-b632", "attributes": { "roles":

    ["DEVELOPER", "MARKETING"] } } } > > PATCH /v1/users/24e811a2-2ad0-46e4-b632
  17. struct RoleModificationRequest: RequestType { typealias Response = SingleContainer<User> let method:

    HTTPMethod = .patch var path: String { return "/users/\(id.uuidString.lowercased())" } let id: UUID let roles: [Role] var payload: RequestPayload { return .init(id: id, type: "users", attributes: roles) } } ͭͷ݁ՌΛड͚औΔܕ 3FRVFTUCPEZΛఆٛ
  18. { "data": { "type": "users", "id": "24e811a2-2ad0-46e4-b632", "attributes": { "roles":

    ["DEVELOPER", "MARKETING"] } } } > > PATCH /v1/users/24e811a2-2ad0-46e4-b632
  19. let uuid = UUID(uuidString: “588ec36e- ba74-11e8-8879-93c782f9ccb3")! let request = RoleModificationRequest(

    id: uuid, roles: [.developer, .marketing] ) client.send(request) { result in switch result { case .success(let container): let modifiedUser: User = container.data print("Name: \(modifiedUser.firstName) \(modifiedUser.lastName)") print("Email: \(modifiedUser.email)") print("Roles: \(modifiedUser.roles)") case .failure(let error): print("Something went wrong") print(String(describing: error)) } }
  20. JSON Web Token • JSON Web Token • https://jwt.io/ •

    JSONΛbase64Τϯίʔυͯ͠ɺൿີ伴Λ࢖ͬͯ҉߸Խ • ެ։伴Λ࢖ͬͯ෮߸
  21. JWT on Swift • Swift੡ͷ·ͱ΋ͳϥΠϒϥϦ͕ͳ͍ • ͋ͬͯ΋ػೳ͕଍Γͳ͍ • ECDSA(ES256)ʹඇରԠ •

    ೚ҙͷϔομʔΛ࣋ͨͤΒΕͳ͍ • OpenSSLͰࣗ෼Ͱ࣮૷͢Δͱେม
  22. import JWT struct JWTEncoder { func encode(issuerID: UUID, keyID: String)

    throws -> String { let object = UnsafeMutablePointer<OpaquePointer?>.allocate(capacity: MemoryLayout<OpaquePointer>.size) jwt_new(object) defer { jwt_free(object.pointee) } let keyPointer = convertToCString(privateKey) defer { keyPointer.deallocate() } jwt_set_alg(object.pointee, JWT_ALG_ES256, keyPointer, Int32(privateKey.utf16.count + 1)) // https://github.com/benmcollins/libjwt/pull/71 jwt_add_header(object.pointee, "kid", keyID) jwt_add_grant(object.pointee, "iss", issuerID.uuidString.lowercased()) let expirationDate = Date().addingTimeInterval(expirationInterval) jwt_add_grant_int(object.pointee, "exp", Int(expirationDate.timeIntervalSince1970)) jwt_add_grant(object.pointee, "aud", "appstoreconnect-v1") guard let encodedCString = jwt_encode_str(object.pointee) else { throw Error.decodeError } return String(cString: encodedCString) } } 4ZTUFN-JCSBSZͱͯ͠ެ։ $ͷؔ਺͕4XJGU͔Β࢖͑Δ
  23. ࢖͍ಓʢΞΠσΟΞʣ • App Store Connectͷݖݶ؅ཧࣗಈԽʁ • TestFlight΁ͷট଴΍഑৴ͷࣗಈԽʁ • Wormhole +

    VaporͰొ࿥ϖʔδͱ͔࡞Εͦ͏ • ূ໌ॻ΍Provisioning Profileͷੜ੒ࣗಈԽʁ