Class: Spaceship::TunesClient

Inherits:
Client
  • Object
show all
Defined in:
lib/spaceship/tunes/tunes_client.rb

Constant Summary

Constants inherited from Client

Client::PROTOCOL_VERSION

Instance Attribute Summary

Attributes inherited from Client

#client, #cookie, #logger

Init and Login collapse

Applications collapse

AppVersions collapse

Build Trains collapse

Submit for Review collapse

Testers collapse

Methods inherited from Client

#UI, #initialize, login, #login, #page_size, #paging, #session?

Constructor Details

This class inherits a constructor from Spaceship::Client

Class Method Details

.hostnameObject



8
9
10
# File 'lib/spaceship/tunes/tunes_client.rb', line 8

def self.hostname
  "https://itunesconnect.apple.com/WebObjects/iTunesConnect.woa/"
end

Instance Method Details

#add_tester_to_app!(tester, app_id) ⇒ Object



328
329
330
# File 'lib/spaceship/tunes/tunes_client.rb', line 328

def add_tester_to_app!(tester, app_id)
  update_tester_from_app!(tester, app_id, true)
end

#app_version(app_id, is_live) ⇒ Object



177
178
179
180
181
182
183
184
# File 'lib/spaceship/tunes/tunes_client.rb', line 177

def app_version(app_id, is_live)
  raise "app_id is required" unless app_id

  v_text = (is_live ? 'live' : nil)

  r = request(:get, "ra/apps/version/#{app_id}", {v: v_text})
  parse_response(r, 'data')
end

#applicationsObject



119
120
121
122
# File 'lib/spaceship/tunes/tunes_client.rb', line 119

def applications
  r = request(:get, 'ra/apps/manageyourapps/summary')
  parse_response(r, 'data')['summaries']
end

#build_trains(app_id) ⇒ Object



204
205
206
207
208
209
# File 'lib/spaceship/tunes/tunes_client.rb', line 204

def build_trains(app_id)
  raise "app_id is required" unless app_id

  r = request(:get, "ra/apps/#{app_id}/trains/")
  data = parse_response(r, 'data')
end

#create_application!(name: nil, primary_language: nil, version: nil, sku: nil, bundle_id: nil, bundle_id_suffix: nil) ⇒ Object

Creates a new application on iTunes Connect

Parameters:

  • name (String) (defaults to: nil)

    : The name of your app as it will appear on the App Store. This can’t be longer than 255 characters.

  • primary_language (String) (defaults to: nil)

    : If localized app information isn’t available in an App Store territory, the information from your primary language will be used instead.

  • version (String) (defaults to: nil)

    : The version number is shown on the App Store and should match the one you used in Xcode.

  • sku (String) (defaults to: nil)

    : A unique ID for your app that is not visible on the App Store.

  • bundle_id (String) (defaults to: nil)

    : The bundle ID must match the one you used in Xcode. It can’t be changed after you submit your first build.



134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/spaceship/tunes/tunes_client.rb', line 134

def create_application!(name: nil, primary_language: nil, version: nil, sku: nil, bundle_id: nil, bundle_id_suffix: nil)
  # First, we need to fetch the data from Apple, which we then modify with the user's values
  r = request(:get, 'ra/apps/create/?appType=ios')
  data = parse_response(r, 'data')

  # Now fill in the values we have
  data['versionString']['value'] = version
  data['newApp']['name']['value'] = name
  data['newApp']['bundleId']['value'] = bundle_id
  data['newApp']['primaryLanguage']['value'] = primary_language || 'English_CA'
  data['newApp']['vendorId']['value'] = sku
  data['newApp']['bundleIdSuffix']['value'] = bundle_id_suffix

  # Now send back the modified hash
  r = request(:post) do |req|
    req.url 'ra/apps/create/?appType=ios'
    req.body = data.to_json
    req.headers['Content-Type'] = 'application/json'
  end
    
  data = parse_response(r, 'data')
  handle_itc_response(data)
end

#create_tester!(tester: nil, email: nil, first_name: nil, last_name: nil, group: nil) ⇒ Object

Parameters:

  • group (String) (defaults to: nil)

    an optional group name



256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
# File 'lib/spaceship/tunes/tunes_client.rb', line 256

def create_tester!(tester: nil, email: nil, first_name: nil, last_name: nil, group: nil) 
  url = tester.url[:create]
  raise "Action not provided for this tester type." unless url

  tester_data = {
        emailAddress: {
          value: email
        }, 
        firstName: {
          value: first_name
        },
        lastName: {
          value: last_name
        },
        testing: {
          value: true
        }
      }
  
  if group
    tester_data[:groups] = [{
                              id: nil,
                              name: {
                                value: group
                              }
                            }]
  end

  data = { testers: [tester_data] }

  r = request(:post) do |req|
    req.url url
    req.body = data.to_json
    req.headers['Content-Type'] = 'application/json'
  end

  data = parse_response(r, 'data')['testers']
  handle_itc_response(data) || data[0]
end

#create_version!(app_id, version_number) ⇒ Object



158
159
160
161
162
163
164
165
166
# File 'lib/spaceship/tunes/tunes_client.rb', line 158

def create_version!(app_id, version_number)
  r = request(:post) do |req|
    req.url "ra/apps/version/create/#{app_id}"
    req.body = { version: version_number.to_s }.to_json
    req.headers['Content-Type'] = 'application/json'
  end

  parse_response(r, 'data')
end

#delete_tester!(tester) ⇒ Object



296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
# File 'lib/spaceship/tunes/tunes_client.rb', line 296

def delete_tester!(tester)
  url = tester.class.url[:delete]
  raise "Action not provided for this tester type." unless url

  data = [
    {
      emailAddress: {
        value: tester.email
      }, 
      firstName: {
        value: tester.first_name
      },
      lastName: {
        value: tester.last_name
      },
      testing: {
        value: false
      }, 
      testerId: tester.tester_id
    }
  ]

  r = request(:post) do |req|
    req.url url
    req.body = data.to_json
    req.headers['Content-Type'] = 'application/json'
  end

  data = parse_response(r, 'data')['testers']
  handle_itc_response(data) || data[0]
end

#get_resolution_center(app_id) ⇒ Object



168
169
170
171
# File 'lib/spaceship/tunes/tunes_client.rb', line 168

def get_resolution_center(app_id)
  r = request(:get, "ra/apps/#{app_id}/resolutionCenter?v=latest")
  data = parse_response(r, 'data')
end

#handle_itc_response(data) ⇒ Object



68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/spaceship/tunes/tunes_client.rb', line 68

def handle_itc_response(data)
  return unless data
  return unless data.kind_of?Hash
 
  if data.fetch('sectionErrorKeys', []).count == 0 and
    data.fetch('sectionInfoKeys', []).count == 0 and 
    data.fetch('sectionWarningKeys', []).count == 0
    
    logger.debug("Request was successful")
  end

  def handle_response_hash(hash)
    errors = []
    if hash.kind_of?Hash
      hash.each do |key, value|
        errors = errors + handle_response_hash(value)

        if key == 'errorKeys' and value.kind_of?Array and value.count > 0
          errors = errors + value
        end
      end
    elsif hash.kind_of?Array
      hash.each do |value|
        errors = errors + handle_response_hash(value)
      end
    else
      # We don't care about simple values
    end
    return errors
  end

  errors = handle_response_hash(data)
  errors = errors + data.fetch('sectionErrorKeys') if data['sectionErrorKeys']

  # Sometimes there is a different kind of error in the JSON response
  different_error = data.fetch('messages', {}).fetch('error', nil)
  errors << different_error if different_error

  raise errors.join(' ') if errors.count > 0 # they are separated by `.` by default

  puts data['sectionInfoKeys'] if data['sectionInfoKeys']
  puts data['sectionWarningKeys'] if data['sectionWarningKeys']

  return data
end

#handle_response_hash(hash) ⇒ Object



79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# File 'lib/spaceship/tunes/tunes_client.rb', line 79

def handle_response_hash(hash)
  errors = []
  if hash.kind_of?Hash
    hash.each do |key, value|
      errors = errors + handle_response_hash(value)

      if key == 'errorKeys' and value.kind_of?Array and value.count > 0
        errors = errors + value
      end
    end
  elsif hash.kind_of?Array
    hash.each do |value|
      errors = errors + handle_response_hash(value)
    end
  else
    # We don't care about simple values
  end
  return errors
end

#login_urlObject

Fetches the latest login URL from iTunes Connect



13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# File 'lib/spaceship/tunes/tunes_client.rb', line 13

def 
  cache_path = "/tmp/spaceship_itc_login_url.txt"
  begin
    cached = File.read(cache_path) 
  rescue Errno::ENOENT
  end
  return cached if cached

  host = "https://itunesconnect.apple.com"
  begin
    url = host + request(:get, self.class.hostname).body.match(/action="(\/WebObjects\/iTunesConnect.woa\/wo\/.*)"/)[1]
    raise "" unless url.length > 0

    File.write(cache_path, url) # TODO
    return url
  rescue => ex
    puts ex
    raise "Could not fetch the login URL from iTunes Connect, the server might be down"
  end
end

#remove_tester_from_app!(tester, app_id) ⇒ Object



332
333
334
# File 'lib/spaceship/tunes/tunes_client.rb', line 332

def remove_tester_from_app!(tester, app_id)
  update_tester_from_app!(tester, app_id, false)
end

#send_app_submission(app_id, data, stage) ⇒ Object



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

def send_app_submission(app_id, data, stage)
  raise "app_id is required" unless app_id

  r = request(:post) do |req|
    req.url "ra/apps/#{app_id}/version/submit/#{stage}"
    req.body = data.to_json
    req.headers['Content-Type'] = 'application/json'
  end
  
  handle_itc_response(r.body['data'])
  parse_response(r, 'data')
end

#send_login_request(user, password) ⇒ Object



34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# File 'lib/spaceship/tunes/tunes_client.rb', line 34

def (user, password)
  response = request(:post, , {
    theAccountName: user,
    theAccountPW: password
  })

  if response['Set-Cookie'] =~ /myacinfo=(\w+);/
    # To use the session properly we'll need the following cookies:
    #  - myacinfo
    #  - woinst
    #  - wosid

    begin
      cooks = response['Set-Cookie']

      to_use = [
        "myacinfo=" + cooks.match(/myacinfo=(\w+)/)[1],
        "woinst=" + cooks.match(/woinst=(\w+)/)[1],
        "wosid=" + cooks.match(/wosid=(\w+)/)[1]
      ]

      @cookie = to_use.join(';')
    rescue => ex
      # User Credentials are wrong
      raise InvalidUserCredentialsError.new(response)
    end
    
    return @client
  else
    # User Credentials are wrong
    raise InvalidUserCredentialsError.new(response)
  end
end

#testers(tester) ⇒ Object



243
244
245
246
247
# File 'lib/spaceship/tunes/tunes_client.rb', line 243

def testers(tester)
  url = tester.url[:index]
  r = request(:get, url)
  parse_response(r, 'data')['testers']
end

#testers_by_app(tester, app_id) ⇒ Object



249
250
251
252
253
# File 'lib/spaceship/tunes/tunes_client.rb', line 249

def testers_by_app(tester, app_id) 
  url = tester.url(app_id)[:index_by_app]
  r = request(:get, url)
  parse_response(r, 'data')['users']
end

#update_app_version!(app_id, is_live, data) ⇒ Object



186
187
188
189
190
191
192
193
194
195
196
197
198
# File 'lib/spaceship/tunes/tunes_client.rb', line 186

def update_app_version!(app_id, is_live, data)
  raise "app_id is required" unless app_id

  v_text = (is_live ? 'live' : nil)

  r = request(:post) do |req|
    req.url "ra/apps/version/save/#{app_id}?v=#{v_text}"
    req.body = data.to_json
    req.headers['Content-Type'] = 'application/json'
  end
  
  handle_itc_response(r.body['data'])
end

#update_build_trains!(app_id, data) ⇒ Object



211
212
213
214
215
216
217
218
219
220
221
# File 'lib/spaceship/tunes/tunes_client.rb', line 211

def update_build_trains!(app_id, data)
  raise "app_id is required" unless app_id

  r = request(:post) do |req|
    req.url "ra/apps/#{app_id}/trains/"
    req.body = data.to_json
    req.headers['Content-Type'] = 'application/json'
  end

  handle_itc_response(r.body['data'])
end