Class: Codes::ItunesConnect

Inherits:
FastlaneCore::ItunesConnect
  • Object
show all
Includes:
Capybara::DSL
Defined in:
lib/codes/itunes_connect/itunes_connect.rb,
lib/codes/itunes_connect.rb,
lib/codes/itunes_connect/itunes_connect_login.rb,
lib/codes/itunes_connect/itunes_connect_helper.rb

Overview

Login code

Defined Under Namespace

Classes: ItunesConnectGeneralError, ItunesConnectLoginError

Constant Summary collapse

PROMO_URL =
'https://itunesconnect.apple.com/WebObjects/iTunesConnect.woa/wa/LCAppPage/viewPromoCodes?adamId=[[app_id]]&platform=[[platform]]'
CODE_URL =
'https://buy.itunes.apple.com/WebObjects/MZFinance.woa/wa/redeemLandingPage?code=[[code]]'
ITUNESCONNECT_URL =
"https://itunesconnect.apple.com/"
APP_DETAILS_URL =
"https://itunesconnect.apple.com/WebObjects/iTunesConnect.woa/ra/ng/app/[[app_id]]"
BUTTON_STRING_NEW_VERSION =
"New Version"
BUTTON_STRING_SUBMIT_FOR_REVIEW =
"Submit for Review"
WAITING_FOR_REVIEW =
"Waiting For Review"

Instance Method Summary collapse

Constructor Details

#initializeItunesConnect

Returns a new instance of ItunesConnect.



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
# File 'lib/codes/itunes_connect/itunes_connect.rb', line 35

def initialize
  super

  return if Helper.is_test?

  Capybara.run_server = false
  Capybara.default_driver = :poltergeist
  Capybara.javascript_driver = :poltergeist
  Capybara.current_driver = :poltergeist
  Capybara.app_host = ITUNESCONNECT_URL

  # Since Apple has some SSL errors, we have to configure the client properly:
  # https://github.com/ariya/phantomjs/issues/11239
  Capybara.register_driver :poltergeist do |a|
    conf = ['--debug=no', '--ignore-ssl-errors=yes', '--ssl-protocol=TLSv1']
    Capybara::Poltergeist::Driver.new(a, {
      phantomjs: Phantomjs.path,
      phantomjs_options: conf,
      phantomjs_logger: File.open("/tmp/poltergeist_log.txt", "a"),
      js_errors: false,
      timeout: 90
    })
  end

  page.driver.headers = { "Accept-Language" => "en" }

  
end

Instance Method Details

#app_platform(app) ⇒ Object



79
80
81
# File 'lib/codes/itunes_connect.rb', line 79

def app_platform(app)
  app['kind'] == 'mac-software' ? 'osx' : 'ios'
end

#click_nextObject



112
113
114
# File 'lib/codes/itunes_connect.rb', line 112

def click_next
  wait_for_elements('input.continueActionButton').first.click
end

#display(args) ⇒ Object



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
# File 'lib/codes/itunes_connect.rb', line 83

def display(args)
  Helper.log.info 'Displaying remaining number of codes promo'

  fetch_app_data args

  # Use Pathname because it correctly handles the distinction between relative paths vs. absolute paths
  output_file_path = Pathname.new(args[:output_file_path]) if args[:output_file_path]
  output_file_path ||= Pathname.new(File.join(Dir.getwd, "#{@app_identifier || @app_id}_codes_info.txt"))
  fail 'Insufficient permissions to write to output file'.red if File.exist?(output_file_path) && !File.writable?(output_file_path)
  visit PROMO_URL.gsub('[[app_id]]', @app_id.to_s).gsub('[[platform]]', @platform)

  begin
    text_fields = wait_for_elements('input[type=text]')
  rescue
    raise "Could not open details page for app #{app_identifier}. Are you sure you are using the correct apple account and have access to this app?".red
  end
  fail 'There should only be a single text input field to specify the number of codes'.red unless text_fields.count == 1

  remaining_divs = wait_for_elements('div#codes_0')
  fail 'There should only be a single text div containing the number of remaining codes'.red unless remaining_divs.count == 1
  remaining = remaining_divs.first.text.split(' ')[0]

  bytes_written = File.write(output_file_path.to_s, remaining, mode: 'a+')
  Helper.log.warn 'Could not write your codes to the codes_info.txt file, but you can still access them from iTunes Connect later' if bytes_written == 0
  Helper.log.info "Added information of quantity of remaining codes to '#{output_file_path}'".green unless bytes_written == 0

  puts remaining
end

#download(args) ⇒ Object

rubocop:disable Metrics/AbcSize



9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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
# File 'lib/codes/itunes_connect.rb', line 9

def download(args)
  number_of_codes = args[:number_of_codes]

  code_or_codes = number_of_codes == 1 ? 'code' : 'codes'
  Helper.log.info "Downloading #{number_of_codes} promo #{code_or_codes}..."

  fetch_app_data args

  # Use Pathname because it correctly handles the distinction between relative paths vs. absolute paths
  output_file_path = Pathname.new(args[:output_file_path]) if args[:output_file_path]
  output_file_path ||= Pathname.new(File.join(Dir.getwd, "#{@app_identifier || @app_id}_codes.txt"))
  fail 'Insufficient permissions to write to output file'.red if File.exist?(output_file_path) && !File.writable?(output_file_path)
  visit PROMO_URL.gsub('[[app_id]]', @app_id.to_s).gsub('[[platform]]', @platform)

  begin
    text_fields = wait_for_elements('input[type=text]')
  rescue
    raise "Could not open details page for app #{@app_identifier}. Are you sure you are using the correct apple account and have access to this app?".red
  end
  fail 'There should only be a single text input field to specify the number of codes'.red unless text_fields.count == 1

  text_fields.first.set(number_of_codes.to_s)
  click_next

  # are there any errors ?
  errors = []
  begin
    errors = wait_for_elements('div[id=LCPurpleSoftwarePageWrapperErrorMessage]')
  rescue
  end
  fail errors.first.text.red unless errors.count == 0

  Helper.log.debug 'Accepting the App Store Volume Custom Code Agreement'
  wait_for_elements('input[type=checkbox]').first.click
  click_next

  # the find(:xpath, "..") gets the parent element of the previous expression
  download_url = wait_for_elements("div[class='large-blue-rect-button']").first.find(:xpath, '..')['href']

  codes, request_date = download_codes(download_url)

  format = args[:format]
  codes = download_format(codes, format, request_date, app) if format

  bytes_written = File.write(output_file_path.to_s, codes, mode: 'a+')
  Helper.log.warn 'Could not write your codes to the codes.txt file, but you can still access them from iTunes Connect later' if bytes_written == 0
  Helper.log.info "Added generated codes to '#{output_file_path}'".green unless  bytes_written == 0

  Helper.log.info "Your codes (requested #{request_date}) were successfully downloaded:".green
  puts codes
end

#download_codes(url) ⇒ Object



116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# File 'lib/codes/itunes_connect.rb', line 116

def download_codes(url)
  host = Capybara.current_session.current_host
  url = URI.join(host, url)
  Helper.log.debug "Downloading promo code file from #{url}"

  cookie_string = ''
  page.driver.cookies.each do |key, cookie|
    cookie_string << "#{cookie.name}=#{cookie.value};"
  end

  page = open(url, 'Cookie' => cookie_string)
  request_date = page.metas['content-disposition'][0].gsub(/.*filename=.*_(.*).txt/, '\\1')
  codes = page.read

  [codes, request_date]
end

#download_format(codes, format, request_date, app) ⇒ Object

rubocop:enable Metrics/AbcSize



62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/codes/itunes_connect.rb', line 62

def download_format(codes, format, request_date, app)
  format = format.gsub(/%([a-z])/, '%{\\1}') # %c => %{c}

  codes = codes.split("\n").map do |code|
    format % {
      c: code,
      b: app['bundleId'],
      d: request_date, # e.g. 20150520110716 / Cupertino timestamp...
      i: app['trackId'],
      n: "\'#{app['trackName']}\'",
      p: app_platform(app),
      u: CODE_URL.gsub('[[code]]', code)
    }
  end
  codes.join("\n") + "\n"
end

#login(user = nil, password = nil) ⇒ bool

Loggs in a user with the given login data on the iTC Frontend. You don’t need to pass a username and password. It will Automatically be fetched using the CredentialsManager::PasswordManager. This method will also automatically be called when triggering other actions like #open_app_page

Parameters:

  • user (String) (defaults to: nil)

    (optional) The username/email address

  • password (String) (defaults to: nil)

    (optional) The password

Returns:

  • (bool)

    true if everything worked fine

Raises:



15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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
# File 'lib/codes/itunes_connect/itunes_connect_login.rb', line 15

def (user = nil, password = nil)
  Helper.log.info "Logging into iTunesConnect"

  user ||= CredentialsManager::PasswordManager.shared_manager.username
  password ||= CredentialsManager::PasswordManager.shared_manager.password

  result = visit ITUNESCONNECT_URL
  raise "Could not open iTunesConnect" unless result['status'] == 'success'

  sleep 3

  if page.has_content? "My Apps"
    # Already logged in
    return true
  end

  begin
    wait_for_elements('#accountpassword')
  rescue
    # when the user is already logged in, this will raise an exception
  end

  fill_in "accountname", with: user
  fill_in "accountpassword", with: password

  begin
    page.evaluate_script "appleConnectForm.submit()"
    sleep 7

    if page.has_content? "My Apps"
      # Everything looks good
    else
      visit current_url # iTC sometimes is super buggy, try reloading the site
      sleep 3
      unless page.has_content? "My Apps"
        raise ItunesConnectLoginError.new("Looks like your login data was correct, but you do not have access to the apps.".red)
      end
    end
  rescue => ex
    Helper.log.debug(ex)
    raise ItunesConnectLoginError.new("Error logging in user #{user} with the given password. Make sure you entered them correctly.".red)
  end

  Helper.log.info "Successfully logged into iTunesConnect"

  true
rescue => ex
  error_occured(ex)
end