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

Fastest trip to AppStore Connect

011714704c4a925e542d426d4cdaa4e3?s=47 giginet
September 18, 2018

Fastest trip to AppStore Connect

011714704c4a925e542d426d4cdaa4e3?s=128

giginet

September 18, 2018
Tweet

Transcript

  1. App Store Connect API iOSDC Reject Conference Day 1 @giginet

  2. • ৄղ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
  3. • 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/
  4. 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
  5. API͕ग़ͨʂʂʂ

  6. Lifecycle Manage Users Design and Develop Deliver Provision Analyze Prepare

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

    bundleIds devices certificates salesReports financeReports betaTesterInvitations buildDeliveries betaGroups users profiles betaTesters
  8. > 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": "kate-bell@mac.com", … }, "relationships": {…}, "links": { "self": "…/v1/users/17cbd794-94a3-c7b0-1051" } } ]
  9. { "data": { "type": "userInvitations", "attributes": { "firstName": "John", "lastName":

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

    ["DEVELOPER", "MARKETING"] } } } > > PATCH /v1/users/24e811a2-2ad0-46e4-b632
  11. > > DELETE /v1/users/24e811a2-2ad0-46e4-b632 HTTP/1.1 204 NO CONTENT

  12. > > DELETE /v1/users/24e811a2-2ad0-46e4-b632 HTTP/1.1 204 NO CONTENT

  13. 88%$ऴྃ௚ޙ

  14. None
  15. None
  16. ·ͩެ։͞Εͯͳ͍ 2018/9/18ݱࡏ

  17. None
  18. None
  19. fastlane/spaceship • fastlaneͷҰ෦ • Ruby • Developer Center/iTunes Connectͷඇެ։APIΛୟ͍ͯΔ

  20. giginet/Wormhole https://github.com/giginet/Wormhole

  21. giginet/Wormhole ✅ 1VSF4XJGUZ ✅ 'BTUFSUIBO4QBDFTIJQ ✅ 5ZQF4BGFE ✅ 4VQQPSU4XJGU1BDLBHF.BOBHFS -JOVY4VQQPSU

  22. giginet/Wormhole ✅ 1VSF4XJGUZ ✅ 'BTUFSUIBO4QBDFTIJQ ✅ 5ZQF4BGFE ✅ 4VQQPSU4XJGU1BDLBHF.BOBHFS -JOVY4VQQPSU

    ☠ "WBJMBCMFUIJT4VNNFS
  23. ࢖͍ํ 1.SwiftPMͰCommand Line ToolΛ࡞੒ 2.WormholeΛґଘʹ૊ΈࠐΉ 3.εΫϦϓτΛSwiftͰॻ͘

  24. ύοέʔδੜ੒

  25. // 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௥Ճ
  26. None
  27. 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͕ඞཁ
  28. HTTP/1.1 200 OK { "data": [{ "type": "users", "id": "17cbd794-94a3-c7b0-1051",

    "attributes": { "firstName": "Kate", "lastName": "Bell", "email": "kate-bell@mac.com", … }, "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
  29. 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] }
  30. HTTP/1.1 200 OK { "data": [{ "type": "users", "id": "17cbd794-94a3-c7b0-1051",

    "attributes": { "firstName": "Kate", "lastName": "Bell", "email": "kate-bell@mac.com", … }, "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
  31. struct GetUsersRequest: RequestType { typealias Response = CollectionContainer<User> let method:

    HTTPMethod = .get let path = "/users" let payload: RequestPayload = .void } ෳ਺ͷ݁ՌΛड͚औΔܕ 3FRVFTUCPEZ͸ۭ
  32. 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)) } } ϢʔβʔͷҰཡ͕औΕΔ
  33. { "data": { "type": "users", "id": "24e811a2-2ad0-46e4-b632", "attributes": { "roles":

    ["DEVELOPER", "MARKETING"] } } } > > PATCH /v1/users/24e811a2-2ad0-46e4-b632
  34. 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Λఆٛ
  35. { "data": { "type": "users", "id": "24e811a2-2ad0-46e4-b632", "attributes": { "roles":

    ["DEVELOPER", "MARKETING"] } } } > > PATCH /v1/users/24e811a2-2ad0-46e4-b632
  36. 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)) } }
  37. ೝূ

  38. JSON Web Token • JSON Web Token • https://jwt.io/ •

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

    ೚ҙͷϔομʔΛ࣋ͨͤΒΕͳ͍ • OpenSSLͰࣗ෼Ͱ࣮૷͢Δͱେม
  41. JWT on Swift • libjwtͱ͍͏CͷϥΠϒϥϦΛSwiftPMܦ༝ͰೖΕͯ࢖ͬͯ ͍Δ • libjwtࣗମͷ࠷৽൛͸HomebrewͰग़ͯͳ͍ͷͰࣗ෼Ͱ Formulaॻ͍ͨ •

    SwiftPM 4.2ͷsystemLibraryͱ͍͏࢓૊ΈΛ࢖͍ͬͯΔ • See Wormhole/JWTEncoder.swift
  42. 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͔Β࢖͑Δ
  43. • Θ͍Θ͍Swift Package Manager • https://speakerdeck.com/giginet/waiwaiswift-package- manager • Θ͍Θ͍swiftc #5

  44. ·ͱΊ

  45. ࢖͍ಓʢΞΠσΟΞʣ • App Store Connectͷݖݶ؅ཧࣗಈԽʁ • TestFlight΁ͷট଴΍഑৴ͷࣗಈԽʁ • Wormhole +

    VaporͰొ࿥ϖʔδͱ͔࡞Εͦ͏ • ূ໌ॻ΍Provisioning Profileͷੜ੒ࣗಈԽʁ
  46. ໾ʹཱͭʁ • υΩϡϝϯτ͕·ͩग़ͯͳͯ͘શ๴͕Θ͔Βͳ͍ • ·ͩՆͰ͸ͳ͍ • ݱঢ়Ͱ΋શͯfastlane(spaceship)ͰͰ͖ͦ͏ɻ͜Ε͔Β

  47. ·ͱΊ • App Store Connect API͕·΋ͳ͘དྷͦ͏ • APIΫϥΠΞϯτ࡞ͬͨ • giginet/Wormhole

    • ࢖͑ΔΑ͏ʹͳͬͨΒ΍͍ͬͯ͘
  48. 2018/9/17ݱࡏ

  49. ͝ਗ਼ௌ ͋Γ͕ͱ͏͍͟͝·ͨ͠