fastlane-plugin-firebase_app_distribution 0.2.5 → 0.2.6.pre.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/fastlane/plugin/firebase_app_distribution/actions/firebase_app_distribution_action.rb +75 -27
- data/lib/fastlane/plugin/firebase_app_distribution/actions/firebase_app_distribution_login.rb +3 -0
- data/lib/fastlane/plugin/firebase_app_distribution/client/aab_certificate.rb +23 -0
- data/lib/fastlane/plugin/firebase_app_distribution/client/app.rb +37 -0
- data/lib/fastlane/plugin/firebase_app_distribution/client/firebase_app_distribution_api_client.rb +31 -39
- data/lib/fastlane/plugin/firebase_app_distribution/helper/firebase_app_distribution_error_message.rb +8 -0
- data/lib/fastlane/plugin/firebase_app_distribution/helper/firebase_app_distribution_helper.rb +9 -0
- data/lib/fastlane/plugin/firebase_app_distribution/version.rb +1 -1
- metadata +6 -4
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: d129a8d78fc5bca84c4e7484dbe02436ee9cd4ef9e9005bec4e1646401b9b143
         | 
| 4 | 
            +
              data.tar.gz: 1677a9d201117796c171ed8a0a9472b69bc8b763bdd5492e3a3fd8f3dae19d84
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 1c6e3502a696a1d759f838028812a526745e6d11fe678e26a829ead65fd8114b4244565695d338b1d60ef34cb1129b70948e87c8d297c72334abc46e9bbc20ec
         | 
| 7 | 
            +
              data.tar.gz: 47504b8df8a313b588aea6c5fa8b7d8cebc5d7ea66117c0025b73f53e6f529bbc443e395c66baa357248c81b864ed81f5cfe46cd957c42c92d03e63e16cbc89a
         | 
    
        data/lib/fastlane/plugin/firebase_app_distribution/actions/firebase_app_distribution_action.rb
    CHANGED
    
    | @@ -13,26 +13,46 @@ require_relative '../helper/firebase_app_distribution_auth_client' | |
| 13 13 | 
             
            module Fastlane
         | 
| 14 14 | 
             
              module Actions
         | 
| 15 15 | 
             
                class FirebaseAppDistributionAction < Action
         | 
| 16 | 
            -
                  FIREBASECMD_ACTION = "appdistribution:distribute".freeze
         | 
| 17 | 
            -
             | 
| 18 16 | 
             
                  extend Auth::FirebaseAppDistributionAuthClient
         | 
| 19 17 | 
             
                  extend Helper::FirebaseAppDistributionHelper
         | 
| 20 18 |  | 
| 21 19 | 
             
                  def self.run(params)
         | 
| 22 | 
            -
                    params.values # to validate all inputs before looking for the ipa/apk
         | 
| 20 | 
            +
                    params.values # to validate all inputs before looking for the ipa/apk/aab
         | 
| 23 21 |  | 
| 24 22 | 
             
                    app_id = app_id_from_params(params)
         | 
| 25 23 | 
             
                    platform = lane_platform || platform_from_app_id(app_id)
         | 
| 26 | 
            -
             | 
| 24 | 
            +
             | 
| 25 | 
            +
                    binary_path = get_binary_path(platform, params)
         | 
| 26 | 
            +
                    UI.user_error!("Couldn't find binary") if binary_path.nil?
         | 
| 27 | 
            +
                    UI.user_error!("Couldn't find binary at path #{binary_path}") unless File.exist?(binary_path)
         | 
| 28 | 
            +
                    binary_type = binary_type_from_path(binary_path)
         | 
| 27 29 |  | 
| 28 30 | 
             
                    auth_token = fetch_auth_token(params[:service_credentials_file], params[:firebase_cli_token])
         | 
| 29 | 
            -
                    fad_api_client = Client::FirebaseAppDistributionApiClient.new(auth_token,  | 
| 31 | 
            +
                    fad_api_client = Client::FirebaseAppDistributionApiClient.new(auth_token, params[:debug])
         | 
| 30 32 |  | 
| 31 | 
            -
                     | 
| 33 | 
            +
                    # If binary is an AAB get FULL view of app which includes the aab_state
         | 
| 34 | 
            +
                    app_view = binary_type == :AAB ? 'FULL' : 'BASIC'
         | 
| 35 | 
            +
                    app = fad_api_client.get_app(app_id, app_view)
         | 
| 36 | 
            +
                    validate_app!(app, binary_type)
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                    release_id = fad_api_client.upload(app.project_number, app_id, binary_path, platform.to_s)
         | 
| 32 39 | 
             
                    if release_id.nil?
         | 
| 33 40 | 
             
                      return
         | 
| 34 41 | 
             
                    end
         | 
| 35 42 |  | 
| 43 | 
            +
                    if binary_type == :AAB && app.aab_certificate.empty?
         | 
| 44 | 
            +
                      updated_app = fad_api_client.get_app(app_id)
         | 
| 45 | 
            +
                      unless updated_app.aab_certificate.empty?
         | 
| 46 | 
            +
                        UI.message("After you upload an AAB for the first time, App Distribution " \
         | 
| 47 | 
            +
                          "generates a new test certificate. All AAB uploads are re-signed with this test " \
         | 
| 48 | 
            +
                          "certificate. Use the certificate fingerprints below to register your app " \
         | 
| 49 | 
            +
                          "signing key with API providers, such as Google Sign-In and Google Maps.\n" \
         | 
| 50 | 
            +
                          "MD-1 certificate fingerprint: #{updated_app.aab_certificate.md5_certificate_hash}\n" \
         | 
| 51 | 
            +
                          "SHA-1 certificate fingerprint: #{updated_app.aab_certificate.sha1_certificate_hash}\n" \
         | 
| 52 | 
            +
                          "SHA-256 certificate fingerprint: #{updated_app.aab_certificate.sha256_certificate_hash}")
         | 
| 53 | 
            +
                      end
         | 
| 54 | 
            +
                    end
         | 
| 55 | 
            +
             | 
| 36 56 | 
             
                    release_notes = get_value_from_value_or_file(params[:release_notes], params[:release_notes_file])
         | 
| 37 57 | 
             
                    fad_api_client.post_notes(app_id, release_id, release_notes)
         | 
| 38 58 |  | 
| @@ -90,37 +110,56 @@ module Fastlane | |
| 90 110 | 
             
                    end
         | 
| 91 111 | 
             
                  end
         | 
| 92 112 |  | 
| 93 | 
            -
                  def self. | 
| 94 | 
            -
                     | 
| 95 | 
            -
             | 
| 96 | 
            -
             | 
| 97 | 
            -
             | 
| 98 | 
            -
             | 
| 99 | 
            -
             | 
| 100 | 
            -
             | 
| 113 | 
            +
                  def self.get_binary_path(platform, params)
         | 
| 114 | 
            +
                    if platform == :ios
         | 
| 115 | 
            +
                      return params[:ipa_path] ||
         | 
| 116 | 
            +
                             Actions.lane_context[SharedValues::IPA_OUTPUT_PATH] ||
         | 
| 117 | 
            +
                             Dir["*.ipa"].sort_by { |x| File.mtime(x) }.last
         | 
| 118 | 
            +
                    end
         | 
| 119 | 
            +
             | 
| 120 | 
            +
                    if platform == :android
         | 
| 121 | 
            +
                      return params[:apk_path] || params[:android_artifact_path] if params[:apk_path] || params[:android_artifact_path]
         | 
| 122 | 
            +
             | 
| 123 | 
            +
                      if params[:android_artifact_type] == 'AAB'
         | 
| 124 | 
            +
                        return Actions.lane_context[SharedValues::GRADLE_AAB_OUTPUT_PATH] ||
         | 
| 125 | 
            +
                               Dir["*.aab"].last ||
         | 
| 126 | 
            +
                               Dir[File.join("app", "build", "outputs", "bundle", "release", "app-release.aab")].last
         | 
| 127 | 
            +
                      end
         | 
| 128 | 
            +
             | 
| 129 | 
            +
                      return Actions.lane_context[SharedValues::GRADLE_APK_OUTPUT_PATH] ||
         | 
| 130 | 
            +
                             Dir["*.apk"].last ||
         | 
| 131 | 
            +
                             Dir[File.join("app", "build", "outputs", "apk", "release", "app-release.apk")].last
         | 
| 101 132 | 
             
                    end
         | 
| 102 133 | 
             
                  end
         | 
| 103 134 |  | 
| 104 | 
            -
                  def self. | 
| 105 | 
            -
                    if  | 
| 106 | 
            -
                       | 
| 135 | 
            +
                  def self.validate_app!(app, binary_type)
         | 
| 136 | 
            +
                    if app.contact_email.nil? || app.contact_email.strip.empty?
         | 
| 137 | 
            +
                      UI.user_error!(ErrorMessage::GET_APP_NO_CONTACT_EMAIL_ERROR)
         | 
| 107 138 | 
             
                    end
         | 
| 108 139 |  | 
| 109 | 
            -
                    if  | 
| 110 | 
            -
                       | 
| 140 | 
            +
                    if binary_type == :AAB && app.aab_state != App::AabState::ACTIVE && app.aab_state != App::AabState::UNAVAILABLE
         | 
| 141 | 
            +
                      case app.aab_state
         | 
| 142 | 
            +
                      when App::AabState::PLAY_ACCOUNT_NOT_LINKED
         | 
| 143 | 
            +
                        UI.user_error!(ErrorMessage::PLAY_ACCOUNT_NOT_LINKED)
         | 
| 144 | 
            +
                      when App::AabState::APP_NOT_PUBLISHED
         | 
| 145 | 
            +
                        UI.user_error!(ErrorMessage::APP_NOT_PUBLISHED)
         | 
| 146 | 
            +
                      when App::AabState::NO_APP_WITH_GIVEN_BUNDLE_ID_IN_PLAY_ACCOUNT
         | 
| 147 | 
            +
                        UI.user_error!(ErrorMessage::NO_APP_WITH_GIVEN_BUNDLE_ID_IN_PLAY_ACCOUNT)
         | 
| 148 | 
            +
                      when App::AabState::PLAY_IAS_TERMS_NOT_ACCEPTED
         | 
| 149 | 
            +
                        UI.user_error!(ErrorMessage::PLAY_IAS_TERMS_NOT_ACCEPTED)
         | 
| 150 | 
            +
                      else
         | 
| 151 | 
            +
                        UI.user_error!(ErrorMessage.aab_upload_error(app.aab_state))
         | 
| 152 | 
            +
                      end
         | 
| 111 153 | 
             
                    end
         | 
| 154 | 
            +
                  end
         | 
| 112 155 |  | 
| 156 | 
            +
                  def self.available_options
         | 
| 113 157 | 
             
                    [
         | 
| 114 158 | 
             
                      # iOS Specific
         | 
| 115 159 | 
             
                      FastlaneCore::ConfigItem.new(key: :ipa_path,
         | 
| 116 160 | 
             
                                                   env_name: "FIREBASEAPPDISTRO_IPA_PATH",
         | 
| 117 161 | 
             
                                                   description: "Path to your IPA file. Optional if you use the _gym_ or _xcodebuild_ action",
         | 
| 118 | 
            -
                                                    | 
| 119 | 
            -
                                                   default_value_dynamic: true,
         | 
| 120 | 
            -
                                                   optional: true,
         | 
| 121 | 
            -
                                                   verify_block: proc do |value|
         | 
| 122 | 
            -
                                                     UI.user_error!("firebase_app_distribution: Couldn't find ipa file at path '#{value}'") unless File.exist?(value)
         | 
| 123 | 
            -
                                                   end),
         | 
| 162 | 
            +
                                                   optional: true),
         | 
| 124 163 | 
             
                      FastlaneCore::ConfigItem.new(key: :googleservice_info_plist_path,
         | 
| 125 164 | 
             
                                                   env_name: "GOOGLESERVICE_INFO_PLIST_PATH",
         | 
| 126 165 | 
             
                                                   description: "Path to your GoogleService-Info.plist file, relative to the archived product path",
         | 
| @@ -130,12 +169,21 @@ module Fastlane | |
| 130 169 | 
             
                      # Android Specific
         | 
| 131 170 | 
             
                      FastlaneCore::ConfigItem.new(key: :apk_path,
         | 
| 132 171 | 
             
                                                   env_name: "FIREBASEAPPDISTRO_APK_PATH",
         | 
| 172 | 
            +
                                                   deprecated: "The apk_path parameter is deprecated. Please use android_artifact_path instead",
         | 
| 133 173 | 
             
                                                   description: "Path to your APK file",
         | 
| 134 | 
            -
                                                    | 
| 174 | 
            +
                                                   optional: true),
         | 
| 175 | 
            +
                      FastlaneCore::ConfigItem.new(key: :android_artifact_path,
         | 
| 176 | 
            +
                                                   env_name: "FIREBASEAPPDISTRO_ANDROID_ARTIFACT_PATH",
         | 
| 177 | 
            +
                                                   description: "Path to your APK or AAB file",
         | 
| 178 | 
            +
                                                   optional: true),
         | 
| 179 | 
            +
                      FastlaneCore::ConfigItem.new(key: :android_artifact_type,
         | 
| 180 | 
            +
                                                   env_name: "FIREBASEAPPDISTRO_ANDROID_ARTIFACT_TYPE",
         | 
| 181 | 
            +
                                                   description: "Android artifact type. Set to 'APK' or 'AAB'. Defaults to 'APK' if not set",
         | 
| 182 | 
            +
                                                   default_value: "APK",
         | 
| 135 183 | 
             
                                                   default_value_dynamic: true,
         | 
| 136 184 | 
             
                                                   optional: true,
         | 
| 137 185 | 
             
                                                   verify_block: proc do |value|
         | 
| 138 | 
            -
                                                     UI.user_error!("firebase_app_distribution:  | 
| 186 | 
            +
                                                     UI.user_error!("firebase_app_distribution: '#{value}' is not a valid value for android_artifact_path. Should be 'APK' or 'AAB'") unless ['APK', 'AAB'].include?(value)
         | 
| 139 187 | 
             
                                                   end),
         | 
| 140 188 | 
             
                      FastlaneCore::ConfigItem.new(key: :app,
         | 
| 141 189 | 
             
                                                   env_name: "FIREBASEAPPDISTRO_APP",
         | 
    
        data/lib/fastlane/plugin/firebase_app_distribution/actions/firebase_app_distribution_login.rb
    CHANGED
    
    | @@ -8,6 +8,9 @@ module Fastlane | |
| 8 8 | 
             
                class FirebaseAppDistributionLoginAction < Action
         | 
| 9 9 | 
             
                  OOB_URI = "urn:ietf:wg:oauth:2.0:oob"
         | 
| 10 10 | 
             
                  SCOPE = "https://www.googleapis.com/auth/cloud-platform"
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                  # In this type of application, the client secret is not treated as a secret.
         | 
| 13 | 
            +
                  # See: https://developers.google.com/identity/protocols/OAuth2InstalledApp
         | 
| 11 14 | 
             
                  CLIENT_ID = "563584335869-fgrhgmd47bqnekij5i8b5pr03ho849e6.apps.googleusercontent.com"
         | 
| 12 15 | 
             
                  CLIENT_SECRET = "j9iVZfS8kkCEFUPaAeJV0sAi"
         | 
| 13 16 |  | 
| @@ -0,0 +1,23 @@ | |
| 1 | 
            +
            class AabCertificate
         | 
| 2 | 
            +
              def initialize(response)
         | 
| 3 | 
            +
                @response = response || {}
         | 
| 4 | 
            +
              end
         | 
| 5 | 
            +
             | 
| 6 | 
            +
              def md5_certificate_hash
         | 
| 7 | 
            +
                @response[:certificateHashMd5]
         | 
| 8 | 
            +
              end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
              def sha1_certificate_hash
         | 
| 11 | 
            +
                @response[:certificateHashSha1]
         | 
| 12 | 
            +
              end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
              def sha256_certificate_hash
         | 
| 15 | 
            +
                @response[:certificateHashSha256]
         | 
| 16 | 
            +
              end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
              def empty?
         | 
| 19 | 
            +
                (md5_certificate_hash.nil? || md5_certificate_hash.empty?) &&
         | 
| 20 | 
            +
                  (sha1_certificate_hash.nil? || sha1_certificate_hash.empty?) &&
         | 
| 21 | 
            +
                  (sha256_certificate_hash.nil? || sha256_certificate_hash.empty?)
         | 
| 22 | 
            +
              end
         | 
| 23 | 
            +
            end
         | 
| @@ -0,0 +1,37 @@ | |
| 1 | 
            +
            require_relative 'aab_certificate'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            class App
         | 
| 4 | 
            +
              # AAB states
         | 
| 5 | 
            +
              class AabState
         | 
| 6 | 
            +
                UNSPECIFIED = "AAB_STATE_UNSPECIFIED"
         | 
| 7 | 
            +
                PLAY_ACCOUNT_NOT_LINKED = "PLAY_ACCOUNT_NOT_LINKED"
         | 
| 8 | 
            +
                NO_APP_WITH_GIVEN_BUNDLE_ID_IN_PLAY_ACCOUNT = "NO_APP_WITH_GIVEN_BUNDLE_ID_IN_PLAY_ACCOUNT"
         | 
| 9 | 
            +
                APP_NOT_PUBLISHED = "APP_NOT_PUBLISHED"
         | 
| 10 | 
            +
                PLAY_IAS_TERMS_NOT_ACCEPTED = "PLAY_IAS_TERMS_NOT_ACCEPTED"
         | 
| 11 | 
            +
                ACTIVE = "ACTIVE"
         | 
| 12 | 
            +
                UNAVAILABLE = "AAB_STATE_UNAVAILABLE"
         | 
| 13 | 
            +
              end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
              attr_reader :aab_certificate
         | 
| 16 | 
            +
             | 
| 17 | 
            +
              def initialize(response)
         | 
| 18 | 
            +
                @response = response
         | 
| 19 | 
            +
                @aab_certificate = AabCertificate.new(response[:aabCertificate])
         | 
| 20 | 
            +
              end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
              def app_id
         | 
| 23 | 
            +
                @response[:appId]
         | 
| 24 | 
            +
              end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
              def project_number
         | 
| 27 | 
            +
                @response[:projectNumber]
         | 
| 28 | 
            +
              end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
              def contact_email
         | 
| 31 | 
            +
                @response[:contactEmail]
         | 
| 32 | 
            +
              end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
              def aab_state
         | 
| 35 | 
            +
                @response[:aabState]
         | 
| 36 | 
            +
              end
         | 
| 37 | 
            +
            end
         | 
    
        data/lib/fastlane/plugin/firebase_app_distribution/client/firebase_app_distribution_api_client.rb
    CHANGED
    
    | @@ -1,10 +1,14 @@ | |
| 1 1 | 
             
            require 'fastlane_core/ui/ui'
         | 
| 2 2 | 
             
            require_relative '../actions/firebase_app_distribution_login'
         | 
| 3 3 | 
             
            require_relative '../client/error_response'
         | 
| 4 | 
            +
            require_relative '../client/app'
         | 
| 5 | 
            +
            require_relative '../helper/firebase_app_distribution_helper'
         | 
| 4 6 |  | 
| 5 7 | 
             
            module Fastlane
         | 
| 6 8 | 
             
              module Client
         | 
| 7 9 | 
             
                class FirebaseAppDistributionApiClient
         | 
| 10 | 
            +
                  include Helper::FirebaseAppDistributionHelper
         | 
| 11 | 
            +
             | 
| 8 12 | 
             
                  BASE_URL = "https://firebaseappdistribution.googleapis.com"
         | 
| 9 13 | 
             
                  TOKEN_CREDENTIAL_URI = "https://oauth2.googleapis.com/token"
         | 
| 10 14 | 
             
                  MAX_POLLING_RETRIES = 60
         | 
| @@ -15,17 +19,9 @@ module Fastlane | |
| 15 19 | 
             
                  APPLICATION_JSON = "application/json"
         | 
| 16 20 | 
             
                  APPLICATION_OCTET_STREAM = "application/octet-stream"
         | 
| 17 21 |  | 
| 18 | 
            -
                  def initialize(auth_token,  | 
| 22 | 
            +
                  def initialize(auth_token, debug = false)
         | 
| 19 23 | 
             
                    @auth_token = auth_token
         | 
| 20 24 | 
             
                    @debug = debug
         | 
| 21 | 
            -
             | 
| 22 | 
            -
                    if platform.nil?
         | 
| 23 | 
            -
                      @binary_type = "IPA/APK"
         | 
| 24 | 
            -
                    elsif platform == :ios
         | 
| 25 | 
            -
                      @binary_type = "IPA"
         | 
| 26 | 
            -
                    else
         | 
| 27 | 
            -
                      @binary_type = "APK"
         | 
| 28 | 
            -
                    end
         | 
| 29 25 | 
             
                  end
         | 
| 30 26 |  | 
| 31 27 | 
             
                  # Enables tester access to the specified app release. Skips this
         | 
| @@ -82,40 +78,29 @@ module Fastlane | |
| 82 78 | 
             
                    UI.success("✅ Posted release notes.")
         | 
| 83 79 | 
             
                  end
         | 
| 84 80 |  | 
| 85 | 
            -
                  #  | 
| 86 | 
            -
                  # projects/<project-number>/apps/<app-id>/releases/-/binaries/<binary-hash>
         | 
| 81 | 
            +
                  # Get app
         | 
| 87 82 | 
             
                  #
         | 
| 88 83 | 
             
                  # args
         | 
| 89 84 | 
             
                  #   app_id - Firebase App ID
         | 
| 90 | 
            -
                  #   binary_path - Absolute path to your app's apk/ipa file
         | 
| 91 85 | 
             
                  #
         | 
| 92 | 
            -
                  # Throws a user_error if  | 
| 93 | 
            -
                   | 
| 94 | 
            -
                  def get_upload_token(app_id, binary_path)
         | 
| 95 | 
            -
                    if binary_path.nil? || !File.exist?(binary_path)
         | 
| 96 | 
            -
                      UI.crash!("#{ErrorMessage.binary_not_found(@binary_type)}: #{binary_path}")
         | 
| 97 | 
            -
                    end
         | 
| 98 | 
            -
                    binary_hash = Digest::SHA256.hexdigest(read_binary(binary_path))
         | 
| 99 | 
            -
             | 
| 86 | 
            +
                  # Throws a user_error if the app hasn't been onboarded to App Distribution
         | 
| 87 | 
            +
                  def get_app(app_id, app_view = 'BASIC')
         | 
| 100 88 | 
             
                    begin
         | 
| 101 | 
            -
                      response = connection.get(v1_apps_url(app_id)) do |request|
         | 
| 89 | 
            +
                      response = connection.get("#{v1_apps_url(app_id)}?appView=#{app_view}") do |request|
         | 
| 102 90 | 
             
                        request.headers[AUTHORIZATION] = "Bearer " + @auth_token
         | 
| 103 91 | 
             
                      end
         | 
| 104 92 | 
             
                    rescue Faraday::ResourceNotFound
         | 
| 105 93 | 
             
                      UI.user_error!("#{ErrorMessage::INVALID_APP_ID}: #{app_id}")
         | 
| 106 94 | 
             
                    end
         | 
| 107 | 
            -
             | 
| 108 | 
            -
                     | 
| 109 | 
            -
                      UI.user_error!(ErrorMessage::GET_APP_NO_CONTACT_EMAIL_ERROR)
         | 
| 110 | 
            -
                    end
         | 
| 111 | 
            -
                    return upload_token_format(response.body[:appId], response.body[:projectNumber], binary_hash)
         | 
| 95 | 
            +
             | 
| 96 | 
            +
                    App.new(response.body)
         | 
| 112 97 | 
             
                  end
         | 
| 113 98 |  | 
| 114 99 | 
             
                  # Uploads the app binary to the Firebase API
         | 
| 115 100 | 
             
                  #
         | 
| 116 101 | 
             
                  # args
         | 
| 117 102 | 
             
                  #   app_id - Firebase App ID
         | 
| 118 | 
            -
                  #   binary_path - Absolute path to your app's apk/ipa file
         | 
| 103 | 
            +
                  #   binary_path - Absolute path to your app's aab/apk/ipa file
         | 
| 119 104 | 
             
                  #   platform - 'android' or 'ios'
         | 
| 120 105 | 
             
                  #
         | 
| 121 106 | 
             
                  # Throws a user_error if the binary file does not exist
         | 
| @@ -126,49 +111,55 @@ module Fastlane | |
| 126 111 | 
             
                      request.headers["X-APP-DISTRO-API-CLIENT-ID"] = "fastlane"
         | 
| 127 112 | 
             
                      request.headers["X-APP-DISTRO-API-CLIENT-TYPE"] =  platform
         | 
| 128 113 | 
             
                      request.headers["X-APP-DISTRO-API-CLIENT-VERSION"] = Fastlane::FirebaseAppDistribution::VERSION
         | 
| 114 | 
            +
                      request.headers["X-GOOG-UPLOAD-FILE-NAME"] = File.basename(binary_path)
         | 
| 115 | 
            +
                      request.headers["X-GOOG-UPLOAD-PROTOCOL"] = "raw"
         | 
| 129 116 | 
             
                    end
         | 
| 130 117 | 
             
                  rescue Errno::ENOENT # Raised when binary_path file does not exist
         | 
| 131 | 
            -
                     | 
| 118 | 
            +
                    binary_type = binary_type_from_path(binary_path)
         | 
| 119 | 
            +
                    UI.user_error!("#{ErrorMessage.binary_not_found(binary_type)}: #{binary_path}")
         | 
| 132 120 | 
             
                  end
         | 
| 133 121 |  | 
| 134 122 | 
             
                  # Uploads the binary file if it has not already been uploaded
         | 
| 135 123 | 
             
                  # Takes at least POLLING_INTERVAL_SECONDS between polling get_upload_status
         | 
| 136 124 | 
             
                  #
         | 
| 137 125 | 
             
                  # args
         | 
| 138 | 
            -
                  #    | 
| 139 | 
            -
                  #    | 
| 126 | 
            +
                  #   project_number - Firebase project number
         | 
| 127 | 
            +
                  #   app_id - Firebase app ID
         | 
| 128 | 
            +
                  #   binary_path - Absolute path to your app's aab/apk/ipa file
         | 
| 140 129 | 
             
                  #
         | 
| 141 130 | 
             
                  # Returns the release_id on a successful release, otherwise returns nil.
         | 
| 142 131 | 
             
                  #
         | 
| 143 132 | 
             
                  # Throws a UI error if the number of polling retries exceeds MAX_POLLING_RETRIES
         | 
| 144 133 | 
             
                  # Crashes if not able to upload the binary
         | 
| 145 | 
            -
                  def upload(app_id, binary_path, platform)
         | 
| 146 | 
            -
                     | 
| 134 | 
            +
                  def upload(project_number, app_id, binary_path, platform)
         | 
| 135 | 
            +
                    binary_type = binary_type_from_path(binary_path)
         | 
| 136 | 
            +
             | 
| 137 | 
            +
                    upload_token = get_upload_token(project_number, app_id, binary_path)
         | 
| 147 138 | 
             
                    upload_status_response = get_upload_status(app_id, upload_token)
         | 
| 148 139 | 
             
                    if upload_status_response.success? || upload_status_response.already_uploaded?
         | 
| 149 | 
            -
                      UI.success("✅ This #{ | 
| 140 | 
            +
                      UI.success("✅ This #{binary_type} has been uploaded before. Skipping upload step.")
         | 
| 150 141 | 
             
                    else
         | 
| 151 142 | 
             
                      unless upload_status_response.in_progress?
         | 
| 152 | 
            -
                        UI.message("⌛ Uploading the #{ | 
| 143 | 
            +
                        UI.message("⌛ Uploading the #{binary_type}.")
         | 
| 153 144 | 
             
                        upload_binary(app_id, binary_path, platform)
         | 
| 154 145 | 
             
                      end
         | 
| 155 146 | 
             
                      MAX_POLLING_RETRIES.times do
         | 
| 156 147 | 
             
                        upload_status_response = get_upload_status(app_id, upload_token)
         | 
| 157 148 | 
             
                        if upload_status_response.success? || upload_status_response.already_uploaded?
         | 
| 158 | 
            -
                          UI.success("✅ Uploaded the #{ | 
| 149 | 
            +
                          UI.success("✅ Uploaded the #{binary_type}.")
         | 
| 159 150 | 
             
                          break
         | 
| 160 151 | 
             
                        elsif upload_status_response.in_progress?
         | 
| 161 152 | 
             
                          sleep(POLLING_INTERVAL_SECONDS)
         | 
| 162 153 | 
             
                        else
         | 
| 163 154 | 
             
                          if !upload_status_response.message.nil?
         | 
| 164 | 
            -
                            UI.user_error!("#{ErrorMessage.upload_binary_error( | 
| 155 | 
            +
                            UI.user_error!("#{ErrorMessage.upload_binary_error(binary_type)}: #{upload_status_response.message}")
         | 
| 165 156 | 
             
                          else
         | 
| 166 | 
            -
                            UI.user_error!(ErrorMessage.upload_binary_error( | 
| 157 | 
            +
                            UI.user_error!(ErrorMessage.upload_binary_error(binary_type))
         | 
| 167 158 | 
             
                          end
         | 
| 168 159 | 
             
                        end
         | 
| 169 160 | 
             
                      end
         | 
| 170 161 | 
             
                      unless upload_status_response.success?
         | 
| 171 | 
            -
                        UI.error("It took longer than expected to process your #{ | 
| 162 | 
            +
                        UI.error("It took longer than expected to process your #{binary_type}, please try again.")
         | 
| 172 163 | 
             
                        return nil
         | 
| 173 164 | 
             
                      end
         | 
| 174 165 | 
             
                    end
         | 
| @@ -211,7 +202,8 @@ module Fastlane | |
| 211 202 | 
             
                    "#{v1_apps_url(app_id)}/upload_status/#{app_token}"
         | 
| 212 203 | 
             
                  end
         | 
| 213 204 |  | 
| 214 | 
            -
                  def  | 
| 205 | 
            +
                  def get_upload_token(project_number, app_id, binary_path)
         | 
| 206 | 
            +
                    binary_hash = Digest::SHA256.hexdigest(read_binary(binary_path))
         | 
| 215 207 | 
             
                    CGI.escape("projects/#{project_number}/apps/#{app_id}/releases/-/binaries/#{binary_hash}")
         | 
| 216 208 | 
             
                  end
         | 
| 217 209 |  | 
    
        data/lib/fastlane/plugin/firebase_app_distribution/helper/firebase_app_distribution_error_message.rb
    CHANGED
    
    | @@ -16,6 +16,14 @@ module ErrorMessage | |
| 16 16 | 
             
              INVALID_RELEASE_ID = "App distribution failed to fetch release with id"
         | 
| 17 17 | 
             
              INVALID_RELEASE_NOTES = "Failed to add release notes"
         | 
| 18 18 | 
             
              SERVICE_CREDENTIALS_ERROR = "App Distribution could not generate credentials from the service credentials file specified. Service Account Path"
         | 
| 19 | 
            +
              PLAY_ACCOUNT_NOT_LINKED = "This project is not linked to a Google Play account."
         | 
| 20 | 
            +
              APP_NOT_PUBLISHED = "This app is not published in the Google Play console."
         | 
| 21 | 
            +
              NO_APP_WITH_GIVEN_BUNDLE_ID_IN_PLAY_ACCOUNT = "App with matching package name does not exist in Google Play."
         | 
| 22 | 
            +
              PLAY_IAS_TERMS_NOT_ACCEPTED = "You must accept the Play Internal App Sharing (IAS) terms to upload AABs."
         | 
| 23 | 
            +
             | 
| 24 | 
            +
              def self.aab_upload_error(aab_state)
         | 
| 25 | 
            +
                "Failed to process the AAB: #{aab_state}"
         | 
| 26 | 
            +
              end
         | 
| 19 27 |  | 
| 20 28 | 
             
              def self.binary_not_found(binary_type)
         | 
| 21 29 | 
             
                "Could not find the #{binary_type}. Make sure you set the #{binary_type} path parameter to point to your #{binary_type}"
         | 
    
        data/lib/fastlane/plugin/firebase_app_distribution/helper/firebase_app_distribution_helper.rb
    CHANGED
    
    | @@ -4,6 +4,15 @@ module Fastlane | |
| 4 4 | 
             
              UI = FastlaneCore::UI unless Fastlane.const_defined?("UI")
         | 
| 5 5 | 
             
              module Helper
         | 
| 6 6 | 
             
                module FirebaseAppDistributionHelper
         | 
| 7 | 
            +
                  def binary_type_from_path(binary_path)
         | 
| 8 | 
            +
                    extension = File.extname(binary_path)
         | 
| 9 | 
            +
                    return :APK if extension == '.apk'
         | 
| 10 | 
            +
                    return :AAB if extension == '.aab'
         | 
| 11 | 
            +
                    return :IPA if extension == '.ipa'
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                    UI.user_error!("Unsupported distribution file format, should be .ipa, .apk or .aab")
         | 
| 14 | 
            +
                  end
         | 
| 15 | 
            +
             | 
| 7 16 | 
             
                  def get_value_from_value_or_file(value, path)
         | 
| 8 17 | 
             
                    if (value.nil? || value.empty?) && !path.nil?
         | 
| 9 18 | 
             
                      begin
         | 
    
        metadata
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: fastlane-plugin-firebase_app_distribution
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 0.2. | 
| 4 | 
            +
              version: 0.2.6.pre.1
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Stefan Natchev
         | 
| @@ -10,7 +10,7 @@ authors: | |
| 10 10 | 
             
            autorequire: 
         | 
| 11 11 | 
             
            bindir: bin
         | 
| 12 12 | 
             
            cert_chain: []
         | 
| 13 | 
            -
            date: 2021- | 
| 13 | 
            +
            date: 2021-03-08 00:00:00.000000000 Z
         | 
| 14 14 | 
             
            dependencies:
         | 
| 15 15 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 16 16 | 
             
              name: pry
         | 
| @@ -152,6 +152,8 @@ files: | |
| 152 152 | 
             
            - lib/fastlane/plugin/firebase_app_distribution.rb
         | 
| 153 153 | 
             
            - lib/fastlane/plugin/firebase_app_distribution/actions/firebase_app_distribution_action.rb
         | 
| 154 154 | 
             
            - lib/fastlane/plugin/firebase_app_distribution/actions/firebase_app_distribution_login.rb
         | 
| 155 | 
            +
            - lib/fastlane/plugin/firebase_app_distribution/client/aab_certificate.rb
         | 
| 156 | 
            +
            - lib/fastlane/plugin/firebase_app_distribution/client/app.rb
         | 
| 155 157 | 
             
            - lib/fastlane/plugin/firebase_app_distribution/client/error_response.rb
         | 
| 156 158 | 
             
            - lib/fastlane/plugin/firebase_app_distribution/client/firebase_app_distribution_api_client.rb
         | 
| 157 159 | 
             
            - lib/fastlane/plugin/firebase_app_distribution/helper/firebase_app_distribution_auth_client.rb
         | 
| @@ -174,9 +176,9 @@ required_ruby_version: !ruby/object:Gem::Requirement | |
| 174 176 | 
             
                  version: '0'
         | 
| 175 177 | 
             
            required_rubygems_version: !ruby/object:Gem::Requirement
         | 
| 176 178 | 
             
              requirements:
         | 
| 177 | 
            -
              - - " | 
| 179 | 
            +
              - - ">"
         | 
| 178 180 | 
             
                - !ruby/object:Gem::Version
         | 
| 179 | 
            -
                  version:  | 
| 181 | 
            +
                  version: 1.3.1
         | 
| 180 182 | 
             
            requirements: []
         | 
| 181 183 | 
             
            rubygems_version: 3.1.4
         | 
| 182 184 | 
             
            signing_key: 
         |