Class: Spaceship::Client

Inherits:
Object
  • Object
show all
Defined in:
lib/spaceship/ui.rb,
lib/spaceship/ui/select_team.rb,
lib/spaceship/client.rb

Defined Under Namespace

Classes: InvalidUserCredentialsError, UnexpectedResponse, UserInterface

Constant Summary collapse

PROTOCOL_VERSION =
"QH65B2"

Instance Attribute Summary collapse

Automatic Paging collapse

Login and Team Selection collapse

Apps collapse

Devices collapse

Certificates collapse

Provisioning Profiles collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeClient

Returns a new instance of Client.



52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# File 'lib/spaceship/client.rb', line 52

def initialize
  @client = Faraday.new("https://developer.apple.com/services-account/#{PROTOCOL_VERSION}/") do |c|
    c.response :json, content_type: /\bjson$/
    c.response :xml, content_type: /\bxml$/
    c.response :plist, content_type: /\bplist$/
    c.adapter Faraday.default_adapter

    if ENV['DEBUG']
      # for debugging only
      # This enables tracking of networking requests using Charles Web Proxy
      c.response :logger
      c.proxy "https://127.0.0.1:8888"
    end
  end
end

Instance Attribute Details

#clientObject (readonly)

Returns the value of attribute client.



18
19
20
# File 'lib/spaceship/client.rb', line 18

def client
  @client
end

Returns the value of attribute cookie.



19
20
21
# File 'lib/spaceship/client.rb', line 19

def cookie
  @cookie
end

#loggerObject

The logger in which all requests are logged /tmp/spaceship.log by default



23
24
25
# File 'lib/spaceship/client.rb', line 23

def logger
  @logger
end

Class Method Details

.login(user = nil, password = nil) ⇒ Spaceship::Client

Authenticates with Apple’s web services. This method has to be called once to generate a valid session. The session will automatically be used from then on.

This method will automatically use the username from the Appfile (if available) and fetch the password from the Keychain (if available)

Parameters:

  • user (String) (defaults to: nil)

    (optional): The username (usually the email address)

  • password (String) (defaults to: nil)

    (optional): The password

Returns:

Raises:

  • InvalidUserCredentialsError: raised if authentication failed



43
44
45
46
47
48
49
50
# File 'lib/spaceship/client.rb', line 43

def self.(user = nil, password = nil)
  instance = self.new
  if instance.(user, password)
    instance
  else
    raise InvalidUserCredentialsError.new
  end
end

Instance Method Details

#api_keyObject

Fetches the latest API Key from the Apple Dev Portal



69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/spaceship/client.rb', line 69

def api_key
  cache_path = "/tmp/spaceship_api_key.txt"
  cached = File.read(cache_path) rescue nil
  return cached if cached

  landing_url = "https://developer.apple.com/membercenter/index.action"
  logger.info("GET: " + landing_url)
  headers = @client.get(landing_url).headers
  results = headers['location'].match(/.*appIdKey=(\h+)/)
  if results.length > 1
    api_key = results[1]
    File.write(cache_path, api_key)
    return api_key
  else
    raise "Could not find latest API Key from the Dev Portal"
  end
end

#appsObject



227
228
229
230
231
232
233
234
235
236
237
# File 'lib/spaceship/client.rb', line 227

def apps
  paging do |page_number|
    r = request(:post, 'account/ios/identifiers/listAppIds.action', {
      teamId: team_id,
      pageNumber: page_number,
      pageSize: page_size,
      sort: 'name=asc'
    })
    parse_response(r, 'appIds')
  end
end

#certificates(types) ⇒ Object



318
319
320
321
322
323
324
325
326
327
328
329
# File 'lib/spaceship/client.rb', line 318

def certificates(types)
  paging do |page_number|
    r = request(:post, 'account/ios/certificate/listCertRequests.action', {
      teamId: team_id,
      types: types.join(','),
      pageNumber: page_number,
      pageSize: page_size,
      sort: 'certRequestStatusCode=asc'
    })
    parse_response(r, 'certRequests')
  end
end

#create_app!(type, name, bundle_id) ⇒ Object



247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
# File 'lib/spaceship/client.rb', line 247

def create_app!(type, name, bundle_id)
  ident_params = case type.to_sym
  when :explicit
    {
      type: 'explicit',
      explicitIdentifier: bundle_id,
      appIdentifierString: bundle_id,
      push: 'on',
      inAppPurchase: 'on',
      gameCenter: 'on'
    }
  when :wildcard
    {
      type: 'wildcard',
      wildcardIdentifier: bundle_id,
      appIdentifierString: bundle_id
    }
  end

  params = {
    appIdName: name,
    teamId: team_id
  }

  params.merge!(ident_params)

  r = request(:post, 'account/ios/identifiers/addAppId.action', params)
  parse_response(r, 'appId')
end

#create_certificate!(type, csr, app_id = nil) ⇒ Object



331
332
333
334
335
336
337
338
339
# File 'lib/spaceship/client.rb', line 331

def create_certificate!(type, csr, app_id = nil)
  r = request(:post, 'account/ios/certificate/submitCertificateRequest.action', {
    teamId: team_id,
    type: type,
    csrContent: csr,
    appIdId: app_id  #optional
  })
  parse_response(r, 'certRequest')
end

#create_device!(device_name, device_id) ⇒ Object



301
302
303
304
305
306
307
308
309
310
311
312
# File 'lib/spaceship/client.rb', line 301

def create_device!(device_name, device_id)
  r = request(:post) do |r|
    r.url "https://developerservices2.apple.com/services/#{PROTOCOL_VERSION}/ios/addDevice.action"
    r.params = {
      teamId: team_id,
      deviceNumber: device_id,
      name: device_name
    }
  end

  parse_response(r, 'device')
end

#create_provisioning_profile!(name, distribution_method, app_id, certificate_ids, device_ids) ⇒ Object



378
379
380
381
382
383
384
385
386
387
388
# File 'lib/spaceship/client.rb', line 378

def create_provisioning_profile!(name, distribution_method, app_id, certificate_ids, device_ids)
  r = request(:post, 'account/ios/profile/createProvisioningProfile.action', {
    teamId: team_id,
    provisioningProfileName: name,
    appIdId: app_id,
    distributionType: distribution_method,
    certificateIds: certificate_ids,
    deviceIds: device_ids
  })
  parse_response(r, 'provisioningProfile')
end

#delete_app!(app_id) ⇒ Object



277
278
279
280
281
282
283
# File 'lib/spaceship/client.rb', line 277

def delete_app!(app_id)
  r = request(:post, 'account/ios/identifiers/deleteAppId.action', {
    teamId: team_id,
    appIdId: app_id
  })
  parse_response(r)
end

#delete_provisioning_profile!(profile_id) ⇒ Object



398
399
400
401
402
403
404
# File 'lib/spaceship/client.rb', line 398

def delete_provisioning_profile!(profile_id)
  r = request(:post, 'account/ios/profile/deleteProvisioningProfile.action', {
    teamId: team_id,
    provisioningProfileId: profile_id
  })
  parse_response(r)
end

#details_for_app(app) ⇒ Object



239
240
241
242
243
244
245
# File 'lib/spaceship/client.rb', line 239

def details_for_app(app)
  r = request(:post, 'account/ios/identifiers/getAppIdDetail.action', {
    teamId: team_id,
    appIdId: app.app_id
  })
  parse_response(r, 'appId')
end

#devicesObject



289
290
291
292
293
294
295
296
297
298
299
# File 'lib/spaceship/client.rb', line 289

def devices
  paging do |page_number|
    r = request(:post, 'account/ios/device/listDevices.action', {
      teamId: team_id,
      pageNumber: page_number,
      pageSize: page_size,
      sort: 'name=asc'
    })
    parse_response(r, 'devices')
  end
end

#download_certificate(certificate_id, type) ⇒ Object



341
342
343
344
345
346
347
348
349
350
# File 'lib/spaceship/client.rb', line 341

def download_certificate(certificate_id, type)
  {type: type, certificate_id: certificate_id}.each { |k, v| raise "#{k} must not be nil" if v.nil? }

  r = request(:post, 'https://developer.apple.com/account/ios/certificate/certificateContentDownload.action', {
    teamId: team_id,
    displayId: certificate_id,
    type: type
  })
  parse_response(r)
end

#download_provisioning_profile(profile_id) ⇒ Object



390
391
392
393
394
395
396
# File 'lib/spaceship/client.rb', line 390

def download_provisioning_profile(profile_id)
  r = request(:get, 'https://developer.apple.com/account/ios/profile/profileContentDownload.action', {
    teamId: team_id,
    displayId: profile_id
  })
  parse_response(r)
end

#in_house?Boolean

Is the current session from an Enterprise In House account?

Returns:

  • (Boolean)


218
219
220
221
# File 'lib/spaceship/client.rb', line 218

def in_house?
  return @in_house unless @in_house.nil?
  @in_house = (team_information['type'] == 'In-House')
end

#login(user = nil, password = nil) ⇒ Spaceship::Client

Authenticates with Apple’s web services. This method has to be called once to generate a valid session. The session will automatically be used from then on.

This method will automatically use the username from the Appfile (if available) and fetch the password from the Keychain (if available)

Parameters:

  • user (String) (defaults to: nil)

    (optional): The username (usually the email address)

  • password (String) (defaults to: nil)

    (optional): The password

Returns:

Raises:

  • InvalidUserCredentialsError: raised if authentication failed



151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
# File 'lib/spaceship/client.rb', line 151

def (user = nil, password = nil)
  if user.to_s.empty? or password.to_s.empty?
    require 'credentials_manager'
    data = CredentialsManager::PasswordManager.shared_manager(user, false)
    user ||= data.username
    password ||= data.password
  end

  if user.to_s.empty? or password.to_s.empty?
    raise InvalidUserCredentialsError.new("No login data provided")
  end

  response = request(:post, "https://idmsa.apple.com/IDMSWebAuth/authenticate", {
    appleId: user,
    accountPassword: password,
    appIdKey: api_key
  })

  if response['Set-Cookie'] =~ /myacinfo=(\w+);/
    @cookie = "myacinfo=#{$1};"
    return @client
  else
    # User Credentials are wrong
    raise InvalidUserCredentialsError.new(response)
  end
end

#page_sizeObject

The page size we want to request, defaults to 500



113
114
115
# File 'lib/spaceship/client.rb', line 113

def page_size
  @page_size ||= 500
end

#pagingObject

Handles the paging for you… for free Just pass a block and use the parameter as page number



119
120
121
122
123
124
125
126
127
128
129
130
131
132
# File 'lib/spaceship/client.rb', line 119

def paging
  page = 0
  results = []
  loop do
    page += 1
    current = yield(page)

    results = results + current

    break if ((current || []).count < page_size) # no more results
  end

  return results
end

#provisioning_profilesObject



365
366
367
368
369
370
371
372
373
374
375
376
# File 'lib/spaceship/client.rb', line 365

def provisioning_profiles
  r = request(:post) do |r|
    r.url "https://developerservices2.apple.com/services/#{PROTOCOL_VERSION}/ios/listProvisioningProfiles.action"
    r.params = {
      teamId: team_id,
      includeInactiveProfiles: true,
      onlyCountLists: true,
    }
  end

  parse_response(r, 'provisioningProfiles')
end

#repair_provisioning_profile!(profile_id, name, distribution_method, app_id, certificate_ids, device_ids) ⇒ Object



406
407
408
409
410
411
412
413
414
415
416
417
418
# File 'lib/spaceship/client.rb', line 406

def repair_provisioning_profile!(profile_id, name, distribution_method, app_id, certificate_ids, device_ids)
  r = request(:post, 'account/ios/profile/regenProvisioningProfile.action', {
    teamId: team_id,
    provisioningProfileId: profile_id,
    provisioningProfileName: name,
    appIdId: app_id,
    distributionType: distribution_method,
    certificateIds: certificate_ids.first, # we are most of the times only allowed to pass one
    deviceIds: device_ids
  })

  parse_response(r, 'provisioningProfile')
end

#revoke_certificate!(certificate_id, type) ⇒ Object



352
353
354
355
356
357
358
359
# File 'lib/spaceship/client.rb', line 352

def revoke_certificate!(certificate_id, type)
  r = request(:post, 'account/ios/certificate/revokeCertificate.action', {
    teamId: team_id,
    certificateId: certificate_id,
    type: type
  })
  parse_response(r, 'certRequests')
end

#select_teamObject

Shows a team selection for the user in the terminal. This should not be called on CI systems



201
202
203
# File 'lib/spaceship/client.rb', line 201

def select_team
  @current_team_id = self.UI.select_team
end

#session?Bool

Returns Do we have a valid session?.

Returns:

  • (Bool)

    Do we have a valid session?



179
180
181
# File 'lib/spaceship/client.rb', line 179

def session?
  !!@cookie
end

#team_idString

Returns The currently selected Team ID.

Returns:

  • (String)

    The currently selected Team ID



190
191
192
193
194
195
196
197
# File 'lib/spaceship/client.rb', line 190

def team_id
  return @current_team_id if @current_team_id

  if teams.count > 1
    puts "The current user is in #{teams.count} teams. Pass a team ID or call `select_team` to choose a team. Using the first one for now."
  end
  @current_team_id ||= teams[0]['teamId']
end

#team_id=(team_id) ⇒ Object

Set a new team ID which will be used from now on



206
207
208
# File 'lib/spaceship/client.rb', line 206

def team_id=(team_id)
  @current_team_id = team_id
end

#team_informationHash

Returns Fetches all information of the currently used team.

Returns:

  • (Hash)

    Fetches all information of the currently used team



211
212
213
214
215
# File 'lib/spaceship/client.rb', line 211

def team_information
  teams.find do |t|
    t['teamId'] == team_id
  end
end

#teamsArray

Returns A list of all available teams.

Returns:

  • (Array)

    A list of all available teams



184
185
186
187
# File 'lib/spaceship/client.rb', line 184

def teams
  r = request(:post, 'account/listTeams.action')
  parse_response(r, 'teams')
end

#UIObject

Public getter for all UI related code



11
12
13
# File 'lib/spaceship/ui.rb', line 11

def UI
  UserInterface.new(self)
end