class Fastlane::Client::FirebaseAppDistributionApiClient

Constants

APPLICATION_JSON
APPLICATION_OCTET_STREAM
AUTHORIZATION
BASE_URL
CLIENT_VERSION
CONTENT_TYPE
MAX_POLLING_RETRIES
POLLING_INTERVAL_SECONDS
TOKEN_CREDENTIAL_URI

Public Class Methods

new(auth_token, debug = false) click to toggle source
# File lib/fastlane/plugin/firebase_app_distribution/client/firebase_app_distribution_api_client.rb, line 23
def initialize(auth_token, debug = false)
  @auth_token = auth_token
  @debug = debug
end

Public Instance Methods

add_testers(project_number, emails) click to toggle source

Create testers

args

project_number - Firebase project number
emails - An array of emails to be created as testers. A maximum of
         1000 testers can be created at a time.
# File lib/fastlane/plugin/firebase_app_distribution/client/firebase_app_distribution_api_client.rb, line 211
def add_testers(project_number, emails)
  payload = { emails: emails }
  connection.post(add_testers_url(project_number), payload.to_json) do |request|
    request.headers[AUTHORIZATION] = "Bearer " + @auth_token
    request.headers[CONTENT_TYPE] = APPLICATION_JSON
    request.headers[CLIENT_VERSION] = client_version_header_value
  end
rescue Faraday::BadRequestError
  UI.user_error!(ErrorMessage::INVALID_EMAIL_ADDRESS)
rescue Faraday::ResourceNotFound
  UI.user_error!(ErrorMessage::INVALID_PROJECT)
rescue Faraday::ClientError => e
  if e.response_status == 429
    UI.user_error!(ErrorMessage::TESTER_LIMIT_VIOLATION)
  else
    raise e
  end
end
distribute(release_name, emails, group_aliases) click to toggle source

Enables tester access to the specified app release. Skips this step if no testers are passed in (emails and group_aliases are nil/empty).

args

release_name - App release resource name, returned by upload_status endpoint
emails - String array of app testers' email addresses
group_aliases - String array of Firebase tester group aliases

Throws a user_error if emails or group_aliases are invalid

# File lib/fastlane/plugin/firebase_app_distribution/client/firebase_app_distribution_api_client.rb, line 37
def distribute(release_name, emails, group_aliases)
  if (emails.nil? || emails.empty?) && (group_aliases.nil? || group_aliases.empty?)
    UI.success("✅ No testers passed in. Skipping this step.")
    return
  end
  payload = { testerEmails: emails, groupAliases: group_aliases }
  begin
    connection.post(distribute_url(release_name), payload.to_json) do |request|
      request.headers[AUTHORIZATION] = "Bearer " + @auth_token
      request.headers[CONTENT_TYPE] = APPLICATION_JSON
    end
  rescue Faraday::ClientError
    UI.user_error!("#{ErrorMessage::INVALID_TESTERS} \nEmails: #{emails} \nGroups: #{group_aliases}")
  end
  UI.success("✅ Added testers/groups.")
end
get_aab_info(app_name) click to toggle source

Get AAB info (Android apps only)

args

app_name - Firebase App resource name

Throws a user_error if the app hasn't been onboarded to App Distribution

# File lib/fastlane/plugin/firebase_app_distribution/client/firebase_app_distribution_api_client.rb, line 91
def get_aab_info(app_name)
  begin
    response = connection.get(aab_info_url(app_name)) do |request|
      request.headers[AUTHORIZATION] = "Bearer " + @auth_token
    end
  rescue Faraday::ResourceNotFound
    UI.user_error!("#{ErrorMessage::INVALID_APP_ID}: #{app_name}")
  end

  AabInfo.new(response.body)
end
get_udids(app_id) click to toggle source

Get tester UDIDs

args

app_name - Firebase App resource name

Returns a list of hashes containing tester device info

# File lib/fastlane/plugin/firebase_app_distribution/client/firebase_app_distribution_api_client.rb, line 193
def get_udids(app_id)
  begin
    response = connection.get(get_udids_url(app_id)) do |request|
      request.headers[AUTHORIZATION] = "Bearer " + @auth_token
    end
  rescue Faraday::ResourceNotFound
    UI.user_error!("#{ErrorMessage::INVALID_APP_ID}: #{app_id}")
  end
  response.body[:testerUdids] || []
end
get_upload_status(operation_name) click to toggle source

Fetches the status of an uploaded binary

args

operation_name - Upload operation name (with binary hash)

Returns the `done` status, as well as a release, error, or nil

# File lib/fastlane/plugin/firebase_app_distribution/client/firebase_app_distribution_api_client.rb, line 180
def get_upload_status(operation_name)
  response = connection.get(upload_status_url(operation_name)) do |request|
    request.headers[AUTHORIZATION] = "Bearer " + @auth_token
  end
  UploadStatusResponse.new(response.body)
end
remove_testers(project_number, emails) click to toggle source

Delete testers

args

project_number - Firebase project number
emails - An array of emails to be deleted as testers. A maximum of
         1000 testers can be deleted at a time.

Returns the number of testers that were deleted

# File lib/fastlane/plugin/firebase_app_distribution/client/firebase_app_distribution_api_client.rb, line 238
def remove_testers(project_number, emails)
  payload = { emails: emails }
  response = connection.post(remove_testers_url(project_number), payload.to_json) do |request|
    request.headers[AUTHORIZATION] = "Bearer " + @auth_token
    request.headers[CONTENT_TYPE] = APPLICATION_JSON
    request.headers[CLIENT_VERSION] = client_version_header_value
  end
  response.body[:emails] ? response.body[:emails].count : 0
rescue Faraday::ResourceNotFound
  UI.user_error!(ErrorMessage::INVALID_PROJECT)
end
update_release_notes(release_name, release_notes) click to toggle source

Update release notes for the specified app release. Skips this step if no notes are passed in (release_notes is nil/empty).

args

release_name - App release resource name, returned by upload_status endpoint
release_notes - String of notes for this release

Throws a user_error if the release_notes are invalid

# File lib/fastlane/plugin/firebase_app_distribution/client/firebase_app_distribution_api_client.rb, line 62
def update_release_notes(release_name, release_notes)
  if release_notes.nil? || release_notes.empty?
    UI.success("✅ No release notes passed in. Skipping this step.")
    return
  end
  begin
    payload = {
      name: release_name,
      releaseNotes: {
        text: release_notes
      }
    }
    connection.patch(update_release_notes_url(release_name), payload.to_json) do |request|
      request.headers[AUTHORIZATION] = "Bearer " + @auth_token
      request.headers[CONTENT_TYPE] = APPLICATION_JSON
    end
  rescue Faraday::ClientError => e
    error = ErrorResponse.new(e.response)
    UI.user_error!("#{ErrorMessage::INVALID_RELEASE_NOTES}: #{error.message}")
  end
  UI.success("✅ Posted release notes.")
end
upload(app_name, binary_path, platform) click to toggle source

Uploads the binary file if it has not already been uploaded Takes at least POLLING_INTERVAL_SECONDS between polling get_upload_status

args

app_name - Firebase App resource name
binary_path - Absolute path to your app's aab/apk/ipa file

Returns the release_name of the uploaded release.

Crashes if the number of polling retries exceeds MAX_POLLING_RETRIES or if the binary cannot be uploaded.

# File lib/fastlane/plugin/firebase_app_distribution/client/firebase_app_distribution_api_client.rb, line 137
def upload(app_name, binary_path, platform)
  binary_type = binary_type_from_path(binary_path)

  UI.message("⌛ Uploading the #{binary_type}.")
  operation_name = upload_binary(app_name, binary_path, platform)

  upload_status_response = get_upload_status(operation_name)
  MAX_POLLING_RETRIES.times do
    if upload_status_response.success?
      if upload_status_response.release_updated?
        UI.success("✅ Uploaded #{binary_type} successfully; updated provisioning profile of existing release #{upload_status_response.release_version}.")
        break
      elsif upload_status_response.release_unmodified?
        UI.success("✅ The same #{binary_type} was found in release #{upload_status_response.release_version} with no changes, skipping.")
        break
      else
        UI.success("✅ Uploaded #{binary_type} successfully and created release #{upload_status_response.release_version}.")
      end
      break
    elsif upload_status_response.in_progress?
      sleep(POLLING_INTERVAL_SECONDS)
      upload_status_response = get_upload_status(operation_name)
    else
      if !upload_status_response.error_message.nil?
        UI.user_error!("#{ErrorMessage.upload_binary_error(binary_type)}: #{upload_status_response.error_message}")
      else
        UI.user_error!(ErrorMessage.upload_binary_error(binary_type))
      end
    end
  end
  unless upload_status_response.success?
    UI.crash!("It took longer than expected to process your #{binary_type}, please try again.")
  end

  upload_status_response.release_name
end
upload_binary(app_name, binary_path, platform) click to toggle source

Uploads the app binary to the Firebase API

args

app_name - Firebase App resource name
binary_path - Absolute path to your app's aab/apk/ipa file
platform - 'android' or 'ios'

Throws a user_error if the binary file does not exist

# File lib/fastlane/plugin/firebase_app_distribution/client/firebase_app_distribution_api_client.rb, line 111
def upload_binary(app_name, binary_path, platform)
  response = connection.post(binary_upload_url(app_name), read_binary(binary_path)) do |request|
    request.headers[AUTHORIZATION] = "Bearer " + @auth_token
    request.headers[CONTENT_TYPE] = APPLICATION_OCTET_STREAM
    request.headers[CLIENT_VERSION] = client_version_header_value
    request.headers["X-Goog-Upload-File-Name"] = File.basename(binary_path)
    request.headers["X-Goog-Upload-Protocol"] = "raw"
  end

  response.body[:name] || ''
rescue Errno::ENOENT # Raised when binary_path file does not exist
  binary_type = binary_type_from_path(binary_path)
  UI.user_error!("#{ErrorMessage.binary_not_found(binary_type)}: #{binary_path}")
end

Private Instance Methods

aab_info_url(app_name) click to toggle source
# File lib/fastlane/plugin/firebase_app_distribution/client/firebase_app_distribution_api_client.rb, line 264
def aab_info_url(app_name)
  "#{v1_apps_url(app_name)}/aabInfo"
end
add_testers_url(project_number) click to toggle source
# File lib/fastlane/plugin/firebase_app_distribution/client/firebase_app_distribution_api_client.rb, line 288
def add_testers_url(project_number)
  "/v1/projects/#{project_number}/testers:batchAdd"
end
binary_upload_url(app_name) click to toggle source
# File lib/fastlane/plugin/firebase_app_distribution/client/firebase_app_distribution_api_client.rb, line 276
def binary_upload_url(app_name)
  "/upload#{v1_apps_url(app_name)}/releases:upload"
end
client_version_header_value() click to toggle source
# File lib/fastlane/plugin/firebase_app_distribution/client/firebase_app_distribution_api_client.rb, line 252
def client_version_header_value
  "fastlane/#{Fastlane::FirebaseAppDistribution::VERSION}"
end
connection() click to toggle source
# File lib/fastlane/plugin/firebase_app_distribution/client/firebase_app_distribution_api_client.rb, line 296
def connection
  @connection ||= Faraday.new(url: BASE_URL) do |conn|
    conn.response(:json, parser_options: { symbolize_names: true })
    conn.response(:raise_error) # raise_error middleware will run before the json middleware
    conn.response(:logger, nil, { headers: false, bodies: { response: true }, log_level: :debug }) if @debug
    conn.adapter(Faraday.default_adapter)
  end
end
distribute_url(release_name) click to toggle source
# File lib/fastlane/plugin/firebase_app_distribution/client/firebase_app_distribution_api_client.rb, line 272
def distribute_url(release_name)
  "/v1/#{release_name}:distribute"
end
get_udids_url(app_id) click to toggle source
# File lib/fastlane/plugin/firebase_app_distribution/client/firebase_app_distribution_api_client.rb, line 284
def get_udids_url(app_id)
  "#{v1alpha_apps_url(app_id)}/testers:getTesterUdids"
end
read_binary(path) click to toggle source
# File lib/fastlane/plugin/firebase_app_distribution/client/firebase_app_distribution_api_client.rb, line 305
def read_binary(path)
  # File must be read in binary mode to work on Windows
  File.open(path, 'rb').read
end
remove_testers_url(project_number) click to toggle source
# File lib/fastlane/plugin/firebase_app_distribution/client/firebase_app_distribution_api_client.rb, line 292
def remove_testers_url(project_number)
  "/v1/projects/#{project_number}/testers:batchRemove"
end
update_release_notes_url(release_name) click to toggle source
# File lib/fastlane/plugin/firebase_app_distribution/client/firebase_app_distribution_api_client.rb, line 268
def update_release_notes_url(release_name)
  "/v1/#{release_name}?updateMask=release_notes.text"
end
upload_status_url(operation_name) click to toggle source
# File lib/fastlane/plugin/firebase_app_distribution/client/firebase_app_distribution_api_client.rb, line 280
def upload_status_url(operation_name)
  "/v1/#{operation_name}"
end
v1_apps_url(app_name) click to toggle source
# File lib/fastlane/plugin/firebase_app_distribution/client/firebase_app_distribution_api_client.rb, line 260
def v1_apps_url(app_name)
  "/v1/#{app_name}"
end
v1alpha_apps_url(app_id) click to toggle source
# File lib/fastlane/plugin/firebase_app_distribution/client/firebase_app_distribution_api_client.rb, line 256
def v1alpha_apps_url(app_id)
  "/v1alpha/apps/#{app_id}"
end