From a7ff4f2268d0b2b7f216adb4b3d6383bf2a81fe9 Mon Sep 17 00:00:00 2001 From: Antonio Pallares Date: Fri, 27 Feb 2026 11:20:52 +0100 Subject: [PATCH 01/22] feat: add CircleCI job for maestro E2E tests Co-Authored-By: Claude Opus 4.6 --- .circleci/config.yml | 30 ++++++++++++++++++++++++++++++ fastlane/Fastfile | 11 +++++++++++ 2 files changed, 41 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index e5fae9c1..dc5dbfc4 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -299,6 +299,29 @@ jobs: name: release command: bundle exec fastlane release + run-maestro-e2e-tests: + <<: *base-mac-job + steps: + - checkout + - install-ruby + - revenuecat/install-gem-mac-dependencies: + cache-version: v1 + - npm-dependencies + - revenuecat/install-maestro + - run: + name: Boot iOS Simulator + command: | + xcrun simctl boot "iPhone 16 Pro" || true + xcrun simctl bootstatus "iPhone 16 Pro" -b + - run: + name: Run Maestro E2E Tests + command: bundle exec fastlane run_maestro_e2e_tests + no_output_timeout: 15m + - store_test_results: + path: fastlane/test_output + - store_artifacts: + path: fastlane/test_output + update-hybrid-common-versions: description: "Creates a PR updating purchases-hybrid-common to latest release" <<: *base-mac-job @@ -386,6 +409,13 @@ workflows: - revenuecat/automatic-bump: ruby_version: "3.2" + maestro-e2e-tests: + when: + or: + - equal: ["maestro_e2e_tests", << pipeline.schedule.name >>] + jobs: + - run-maestro-e2e-tests + update-hybrid-common-versions: when: equal: [ upgrade-hybrid-common, << pipeline.parameters.action >> ] diff --git a/fastlane/Fastfile b/fastlane/Fastfile index f5063b86..7c76c4fa 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -138,6 +138,17 @@ lane :update_hybrid_common do |options| ) end +desc "Run maestro E2E tests" +lane :run_maestro_e2e_tests do + Dir.chdir("e2e-tests/MaestroTestApp") do + sh("find www -type f -name '*.js' -exec sed -i '' 's/MAESTRO_TESTS_REVENUECAT_API_KEY/'\"$RC_MAESTRO_E2E_API_KEY\"'/g' {} +") + sh("cordova platform add ios") + sh("cordova build ios --emulator") + sh("xcrun simctl install booted platforms/ios/build/emulator/MaestroTestApp.app") + end + sh("maestro test --format junit --output test_output/report.xml e2e-tests/maestro/") +end + desc "Generate docs" lane :generate_docs do version_number = current_version_number From 8fcc153bfe82db5b4e0aaf4d0a1db48903e2605e Mon Sep 17 00:00:00 2001 From: Antonio Pallares Date: Fri, 27 Feb 2026 11:40:55 +0100 Subject: [PATCH 02/22] fix: add CircleCI context and narrow sed targeting Co-Authored-By: Claude Opus 4.6 --- .circleci/config.yml | 4 +++- fastlane/Fastfile | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index dc5dbfc4..a3c9372a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -414,7 +414,9 @@ workflows: or: - equal: ["maestro_e2e_tests", << pipeline.schedule.name >>] jobs: - - run-maestro-e2e-tests + - run-maestro-e2e-tests: + context: + - maestro-e2e-tests update-hybrid-common-versions: when: diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 7c76c4fa..ed2fc1b2 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -141,7 +141,7 @@ end desc "Run maestro E2E tests" lane :run_maestro_e2e_tests do Dir.chdir("e2e-tests/MaestroTestApp") do - sh("find www -type f -name '*.js' -exec sed -i '' 's/MAESTRO_TESTS_REVENUECAT_API_KEY/'\"$RC_MAESTRO_E2E_API_KEY\"'/g' {} +") + sh("sed -i '' 's/MAESTRO_TESTS_REVENUECAT_API_KEY/'\"$RC_MAESTRO_E2E_API_KEY\"'/g' www/js/app.js") sh("cordova platform add ios") sh("cordova build ios --emulator") sh("xcrun simctl install booted platforms/ios/build/emulator/MaestroTestApp.app") From c9e5dd8c1cc69b927ae65afba174859c7845a9bc Mon Sep 17 00:00:00 2001 From: Antonio Pallares Date: Wed, 25 Mar 2026 16:34:28 +0100 Subject: [PATCH 03/22] fix: use e2e-tests context and production App Store API key for iOS maestro tests Co-Authored-By: Claude Opus 4.6 Made-with: Cursor --- .circleci/config.yml | 1 + fastlane/Fastfile | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index a3c9372a..5723beee 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -417,6 +417,7 @@ workflows: - run-maestro-e2e-tests: context: - maestro-e2e-tests + - e2e-tests update-hybrid-common-versions: when: diff --git a/fastlane/Fastfile b/fastlane/Fastfile index ed2fc1b2..6a1b2f3a 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -141,7 +141,7 @@ end desc "Run maestro E2E tests" lane :run_maestro_e2e_tests do Dir.chdir("e2e-tests/MaestroTestApp") do - sh("sed -i '' 's/MAESTRO_TESTS_REVENUECAT_API_KEY/'\"$RC_MAESTRO_E2E_API_KEY\"'/g' www/js/app.js") + sh("sed -i '' 's/MAESTRO_TESTS_REVENUECAT_API_KEY/'\"$RC_E2E_TEST_API_KEY_PRODUCTION_APP_STORE\"'/g' www/js/app.js") sh("cordova platform add ios") sh("cordova build ios --emulator") sh("xcrun simctl install booted platforms/ios/build/emulator/MaestroTestApp.app") From 887848fe0d76cbce24a77dea70a8645056a7f2c5 Mon Sep 17 00:00:00 2001 From: Antonio Pallares Date: Wed, 25 Mar 2026 16:48:16 +0100 Subject: [PATCH 04/22] feat: split maestro e2e tests into separate iOS and Android workflows Split the single maestro-e2e-tests workflow into two independent workflows (maestro-e2e-tests-ios and maestro-e2e-tests-android) so they run in parallel on their respective platforms. Made-with: Cursor --- .circleci/config.yml | 50 +++++++++++++++++++++++++++++++++++++++----- fastlane/Fastfile | 15 +++++++++++-- 2 files changed, 58 insertions(+), 7 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 5723beee..37c056da 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -299,7 +299,7 @@ jobs: name: release command: bundle exec fastlane release - run-maestro-e2e-tests: + run-maestro-e2e-tests-ios: <<: *base-mac-job steps: - checkout @@ -314,8 +314,38 @@ jobs: xcrun simctl boot "iPhone 16 Pro" || true xcrun simctl bootstatus "iPhone 16 Pro" -b - run: - name: Run Maestro E2E Tests - command: bundle exec fastlane run_maestro_e2e_tests + name: Run Maestro E2E Tests (iOS) + command: bundle exec fastlane run_maestro_e2e_tests_ios + no_output_timeout: 15m + - store_test_results: + path: fastlane/test_output + - store_artifacts: + path: fastlane/test_output + + run-maestro-e2e-tests-android: + machine: + image: android:2024.11.1 + resource_class: xlarge + steps: + - checkout + - run: + name: Install Ruby and Bundler + command: | + gem install bundler + bundle install + - npm-dependencies + - revenuecat/install-maestro + - run: + name: Create and start Android emulator + command: | + sdkmanager "system-images;android-34;google_apis;x86_64" + echo "no" | avdmanager create avd -n test-e2e -k "system-images;android-34;google_apis;x86_64" --force + nohup emulator -avd test-e2e -no-audio -no-boot-anim -no-window -gpu swiftshader_indirect & + adb wait-for-device + adb shell input keyevent 82 + - run: + name: Run Maestro E2E Tests (Android) + command: bundle exec fastlane run_maestro_e2e_tests_android no_output_timeout: 15m - store_test_results: path: fastlane/test_output @@ -409,12 +439,22 @@ workflows: - revenuecat/automatic-bump: ruby_version: "3.2" - maestro-e2e-tests: + maestro-e2e-tests-ios: + when: + or: + - equal: ["maestro_e2e_tests", << pipeline.schedule.name >>] + jobs: + - run-maestro-e2e-tests-ios: + context: + - maestro-e2e-tests + - e2e-tests + + maestro-e2e-tests-android: when: or: - equal: ["maestro_e2e_tests", << pipeline.schedule.name >>] jobs: - - run-maestro-e2e-tests: + - run-maestro-e2e-tests-android: context: - maestro-e2e-tests - e2e-tests diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 6a1b2f3a..8e7c75da 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -138,8 +138,8 @@ lane :update_hybrid_common do |options| ) end -desc "Run maestro E2E tests" -lane :run_maestro_e2e_tests do +desc "Run maestro E2E tests on iOS" +lane :run_maestro_e2e_tests_ios do Dir.chdir("e2e-tests/MaestroTestApp") do sh("sed -i '' 's/MAESTRO_TESTS_REVENUECAT_API_KEY/'\"$RC_E2E_TEST_API_KEY_PRODUCTION_APP_STORE\"'/g' www/js/app.js") sh("cordova platform add ios") @@ -149,6 +149,17 @@ lane :run_maestro_e2e_tests do sh("maestro test --format junit --output test_output/report.xml e2e-tests/maestro/") end +desc "Run maestro E2E tests on Android" +lane :run_maestro_e2e_tests_android do + Dir.chdir("e2e-tests/MaestroTestApp") do + sh("sed -i 's/MAESTRO_TESTS_REVENUECAT_API_KEY/'\"$RC_E2E_TEST_API_KEY_PRODUCTION_PLAY_STORE\"'/g' www/js/app.js") + sh("cordova platform add android") + sh("cordova build android") + sh("adb install platforms/android/app/build/outputs/apk/debug/app-debug.apk") + end + sh("maestro test --format junit --output test_output/report.xml e2e-tests/maestro/") +end + desc "Generate docs" lane :generate_docs do version_number = current_version_number From 872de739a371ff958d6c642e9bce98317627c5bf Mon Sep 17 00:00:00 2001 From: Antonio Pallares Date: Wed, 25 Mar 2026 17:45:56 +0100 Subject: [PATCH 05/22] feat: pass MAESTRO_STORE env var to maestro tests in Fastlane lanes Made-with: Cursor --- fastlane/Fastfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 8e7c75da..c2a22afc 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -146,7 +146,7 @@ lane :run_maestro_e2e_tests_ios do sh("cordova build ios --emulator") sh("xcrun simctl install booted platforms/ios/build/emulator/MaestroTestApp.app") end - sh("maestro test --format junit --output test_output/report.xml e2e-tests/maestro/") + sh("maestro test -e MAESTRO_STORE=app_store --format junit --output test_output/report.xml e2e-tests/maestro/") end desc "Run maestro E2E tests on Android" @@ -157,7 +157,7 @@ lane :run_maestro_e2e_tests_android do sh("cordova build android") sh("adb install platforms/android/app/build/outputs/apk/debug/app-debug.apk") end - sh("maestro test --format junit --output test_output/report.xml e2e-tests/maestro/") + sh("maestro test -e MAESTRO_STORE=test_store --format junit --output test_output/report.xml e2e-tests/maestro/") end desc "Generate docs" From 8840ef99056ad7a809dc45c29bf5ddeeb2572fc2 Mon Sep 17 00:00:00 2001 From: Antonio Pallares Date: Wed, 25 Mar 2026 17:50:40 +0100 Subject: [PATCH 06/22] fix: use RC_E2E_TEST_API_KEY_PRODUCTION_TEST_STORE for Android maestro tests Made-with: Cursor --- fastlane/Fastfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index c2a22afc..5784c3e8 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -152,7 +152,7 @@ end desc "Run maestro E2E tests on Android" lane :run_maestro_e2e_tests_android do Dir.chdir("e2e-tests/MaestroTestApp") do - sh("sed -i 's/MAESTRO_TESTS_REVENUECAT_API_KEY/'\"$RC_E2E_TEST_API_KEY_PRODUCTION_PLAY_STORE\"'/g' www/js/app.js") + sh("sed -i 's/MAESTRO_TESTS_REVENUECAT_API_KEY/'\"$RC_E2E_TEST_API_KEY_PRODUCTION_TEST_STORE\"'/g' www/js/app.js") sh("cordova platform add android") sh("cordova build android") sh("adb install platforms/android/app/build/outputs/apk/debug/app-debug.apk") From 58ad9459532fbf14a5de980f4f855c2038bff08c Mon Sep 17 00:00:00 2001 From: Antonio Pallares Date: Mon, 30 Mar 2026 10:32:27 +0200 Subject: [PATCH 07/22] refactor: use test store for all maestro e2e tests (iOS and Android) Made-with: Cursor --- fastlane/Fastfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 5784c3e8..8c178788 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -141,12 +141,12 @@ end desc "Run maestro E2E tests on iOS" lane :run_maestro_e2e_tests_ios do Dir.chdir("e2e-tests/MaestroTestApp") do - sh("sed -i '' 's/MAESTRO_TESTS_REVENUECAT_API_KEY/'\"$RC_E2E_TEST_API_KEY_PRODUCTION_APP_STORE\"'/g' www/js/app.js") + sh("sed -i '' 's/MAESTRO_TESTS_REVENUECAT_API_KEY/'\"$RC_E2E_TEST_API_KEY_PRODUCTION_TEST_STORE\"'/g' www/js/app.js") sh("cordova platform add ios") sh("cordova build ios --emulator") sh("xcrun simctl install booted platforms/ios/build/emulator/MaestroTestApp.app") end - sh("maestro test -e MAESTRO_STORE=app_store --format junit --output test_output/report.xml e2e-tests/maestro/") + sh("maestro test --format junit --output test_output/report.xml e2e-tests/maestro/") end desc "Run maestro E2E tests on Android" @@ -157,7 +157,7 @@ lane :run_maestro_e2e_tests_android do sh("cordova build android") sh("adb install platforms/android/app/build/outputs/apk/debug/app-debug.apk") end - sh("maestro test -e MAESTRO_STORE=test_store --format junit --output test_output/report.xml e2e-tests/maestro/") + sh("maestro test --format junit --output test_output/report.xml e2e-tests/maestro/") end desc "Generate docs" From 0cfb6acc045200d60c2bf635bf9a933e604b34bb Mon Sep 17 00:00:00 2001 From: Antonio Pallares Date: Mon, 30 Mar 2026 18:57:06 +0200 Subject: [PATCH 08/22] Add Maestro e2e test jobs to build-test PR workflow Made-with: Cursor --- .circleci/config.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 37c056da..903a2dad 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -379,6 +379,14 @@ workflows: equal: [ scheduled_pipeline, << pipeline.trigger_source >> ] jobs: - runtest + - run-maestro-e2e-tests-ios: + context: + - maestro-e2e-tests + - e2e-tests + - run-maestro-e2e-tests-android: + context: + - maestro-e2e-tests + - e2e-tests deploy: when: From c327b2e4f1a8c3d6a66b11285603e6aa1ada6104 Mon Sep 17 00:00:00 2001 From: Antonio Pallares Date: Mon, 30 Mar 2026 19:15:39 +0200 Subject: [PATCH 09/22] Fix Fastlane path resolution and Android emulator boot timing - Prefix Dir.chdir and maestro test paths with ../ since Fastlane runs from the fastlane/ subdirectory - Wait for sys.boot_completed before sending keyevent to emulator Made-with: Cursor --- .circleci/config.yml | 1 + fastlane/Fastfile | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 903a2dad..35dce1e5 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -342,6 +342,7 @@ jobs: echo "no" | avdmanager create avd -n test-e2e -k "system-images;android-34;google_apis;x86_64" --force nohup emulator -avd test-e2e -no-audio -no-boot-anim -no-window -gpu swiftshader_indirect & adb wait-for-device + adb shell 'while [[ -z $(getprop sys.boot_completed) ]]; do sleep 1; done' adb shell input keyevent 82 - run: name: Run Maestro E2E Tests (Android) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 8c178788..78163352 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -140,24 +140,24 @@ end desc "Run maestro E2E tests on iOS" lane :run_maestro_e2e_tests_ios do - Dir.chdir("e2e-tests/MaestroTestApp") do + Dir.chdir("../e2e-tests/MaestroTestApp") do sh("sed -i '' 's/MAESTRO_TESTS_REVENUECAT_API_KEY/'\"$RC_E2E_TEST_API_KEY_PRODUCTION_TEST_STORE\"'/g' www/js/app.js") sh("cordova platform add ios") sh("cordova build ios --emulator") sh("xcrun simctl install booted platforms/ios/build/emulator/MaestroTestApp.app") end - sh("maestro test --format junit --output test_output/report.xml e2e-tests/maestro/") + sh("maestro test --format junit --output test_output/report.xml ../e2e-tests/maestro/") end desc "Run maestro E2E tests on Android" lane :run_maestro_e2e_tests_android do - Dir.chdir("e2e-tests/MaestroTestApp") do + Dir.chdir("../e2e-tests/MaestroTestApp") do sh("sed -i 's/MAESTRO_TESTS_REVENUECAT_API_KEY/'\"$RC_E2E_TEST_API_KEY_PRODUCTION_TEST_STORE\"'/g' www/js/app.js") sh("cordova platform add android") sh("cordova build android") sh("adb install platforms/android/app/build/outputs/apk/debug/app-debug.apk") end - sh("maestro test --format junit --output test_output/report.xml e2e-tests/maestro/") + sh("maestro test --format junit --output test_output/report.xml ../e2e-tests/maestro/") end desc "Generate docs" From f22c3173ed6dcb76fb535cf68886f136488bc754 Mon Sep 17 00:00:00 2001 From: Antonio Pallares Date: Mon, 30 Mar 2026 19:19:39 +0200 Subject: [PATCH 10/22] Use Xcode 26.3 and iPhone 17 simulator for Maestro iOS e2e tests Made-with: Cursor --- .circleci/config.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 35dce1e5..5bb3d991 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -301,6 +301,8 @@ jobs: run-maestro-e2e-tests-ios: <<: *base-mac-job + macos: + xcode: '26.3.0' steps: - checkout - install-ruby @@ -311,8 +313,8 @@ jobs: - run: name: Boot iOS Simulator command: | - xcrun simctl boot "iPhone 16 Pro" || true - xcrun simctl bootstatus "iPhone 16 Pro" -b + xcrun simctl boot "iPhone 17" || true + xcrun simctl bootstatus "iPhone 17" -b - run: name: Run Maestro E2E Tests (iOS) command: bundle exec fastlane run_maestro_e2e_tests_ios From 2e78b30be7d0ed9545b293dd3923adcf413639b7 Mon Sep 17 00:00:00 2001 From: Antonio Pallares Date: Mon, 30 Mar 2026 23:39:44 +0200 Subject: [PATCH 11/22] Fix Cordova Maestro CI: platform add idempotency, Ruby, emulator - Make cordova platform add idempotent (|| true) since platforms are already checked into the repo - Install Ruby 3.2.0 on Android CI machine via rbenv - Split Android build/test and use circleci/android orb for emulator - Add --test-output-dir for Maestro screenshots and artifacts - Add takeScreenshot before assertions for debugging Made-with: Cursor --- .circleci/config.yml | 24 +++++++++++-------- .../e2e_tests/purchase_through_paywall.yaml | 2 ++ fastlane/Fastfile | 20 ++++++++++------ 3 files changed, 29 insertions(+), 17 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 5bb3d991..f396b9d4 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -333,19 +333,23 @@ jobs: - run: name: Install Ruby and Bundler command: | - gem install bundler + rbenv install 3.2.0 --skip-existing + rbenv global 3.2.0 + gem install bundler --no-document bundle install - npm-dependencies - - revenuecat/install-maestro - run: - name: Create and start Android emulator - command: | - sdkmanager "system-images;android-34;google_apis;x86_64" - echo "no" | avdmanager create avd -n test-e2e -k "system-images;android-34;google_apis;x86_64" --force - nohup emulator -avd test-e2e -no-audio -no-boot-anim -no-window -gpu swiftshader_indirect & - adb wait-for-device - adb shell 'while [[ -z $(getprop sys.boot_completed) ]]; do sleep 1; done' - adb shell input keyevent 82 + name: Build Maestro app (Android) + command: bundle exec fastlane build_maestro_app_android + no_output_timeout: 15m + - android/create-avd: + avd-name: test-e2e + system-image: system-images;android-34;google_apis;x86_64 + install: true + - android/start-emulator: + avd-name: test-e2e + post-emulator-launch-assemble-command: "" + - revenuecat/install-maestro - run: name: Run Maestro E2E Tests (Android) command: bundle exec fastlane run_maestro_e2e_tests_android diff --git a/e2e-tests/maestro/e2e_tests/purchase_through_paywall.yaml b/e2e-tests/maestro/e2e_tests/purchase_through_paywall.yaml index c8c77376..2425182e 100644 --- a/e2e-tests/maestro/e2e_tests/purchase_through_paywall.yaml +++ b/e2e-tests/maestro/e2e_tests/purchase_through_paywall.yaml @@ -9,9 +9,11 @@ name: Purchase through paywall - clearState - pressKey: home - launchApp +- takeScreenshot: purchase_through_paywall - Initial state after launch - extendedWaitUntil: visible: "Test Cases" timeout: 30000 +- takeScreenshot: purchase_through_paywall - After Test Cases visible - assertVisible: "Test Cases" - tapOn: text: "Purchase through paywall" diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 78163352..a7d507b7 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -142,22 +142,28 @@ desc "Run maestro E2E tests on iOS" lane :run_maestro_e2e_tests_ios do Dir.chdir("../e2e-tests/MaestroTestApp") do sh("sed -i '' 's/MAESTRO_TESTS_REVENUECAT_API_KEY/'\"$RC_E2E_TEST_API_KEY_PRODUCTION_TEST_STORE\"'/g' www/js/app.js") - sh("cordova platform add ios") + sh("cordova platform add ios 2>/dev/null || true") sh("cordova build ios --emulator") sh("xcrun simctl install booted platforms/ios/build/emulator/MaestroTestApp.app") end - sh("maestro test --format junit --output test_output/report.xml ../e2e-tests/maestro/") + sh("mkdir -p test_output") + sh("maestro test --format junit --output test_output/report.xml --test-output-dir test_output ../e2e-tests/maestro/") end -desc "Run maestro E2E tests on Android" -lane :run_maestro_e2e_tests_android do +desc "Build maestro E2E test app for Android" +lane :build_maestro_app_android do Dir.chdir("../e2e-tests/MaestroTestApp") do sh("sed -i 's/MAESTRO_TESTS_REVENUECAT_API_KEY/'\"$RC_E2E_TEST_API_KEY_PRODUCTION_TEST_STORE\"'/g' www/js/app.js") - sh("cordova platform add android") + sh("cordova platform add android 2>/dev/null || true") sh("cordova build android") - sh("adb install platforms/android/app/build/outputs/apk/debug/app-debug.apk") end - sh("maestro test --format junit --output test_output/report.xml ../e2e-tests/maestro/") +end + +desc "Run maestro E2E tests on Android (emulator must be running)" +lane :run_maestro_e2e_tests_android do + sh("adb install ../e2e-tests/MaestroTestApp/platforms/android/app/build/outputs/apk/debug/app-debug.apk") + sh("mkdir -p test_output") + sh("maestro test --format junit --output test_output/report.xml --test-output-dir test_output ../e2e-tests/maestro/") end desc "Generate docs" From f651faec60d3f5e68d84909598345033ac7fd66a Mon Sep 17 00:00:00 2001 From: Antonio Pallares Date: Mon, 30 Mar 2026 23:48:23 +0200 Subject: [PATCH 12/22] Fix Cordova iOS: install cocoapods gem for cordova build The rbenv Ruby 3.2.0 doesn't have cocoapods pre-installed. cordova build ios calls pod install which needs the pod command. Made-with: Cursor --- fastlane/Fastfile | 1 + 1 file changed, 1 insertion(+) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index a7d507b7..71bcc4f9 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -142,6 +142,7 @@ desc "Run maestro E2E tests on iOS" lane :run_maestro_e2e_tests_ios do Dir.chdir("../e2e-tests/MaestroTestApp") do sh("sed -i '' 's/MAESTRO_TESTS_REVENUECAT_API_KEY/'\"$RC_E2E_TEST_API_KEY_PRODUCTION_TEST_STORE\"'/g' www/js/app.js") + sh("gem install cocoapods --no-document") sh("cordova platform add ios 2>/dev/null || true") sh("cordova build ios --emulator") sh("xcrun simctl install booted platforms/ios/build/emulator/MaestroTestApp.app") From fcafbfd7fdd29aaa119641f8601e82fb0e1879dc Mon Sep 17 00:00:00 2001 From: Antonio Pallares Date: Tue, 31 Mar 2026 00:12:02 +0200 Subject: [PATCH 13/22] Fix iOS build: use unbundled env for cordova build cordova build ios calls pod install internally, but running inside Bundler's context blocked access to the system cocoapods gem. Use Bundler.with_unbundled_env to temporarily escape Bundler's environment for the cordova build command. Made-with: Cursor --- fastlane/Fastfile | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 71bcc4f9..60190568 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -142,9 +142,11 @@ desc "Run maestro E2E tests on iOS" lane :run_maestro_e2e_tests_ios do Dir.chdir("../e2e-tests/MaestroTestApp") do sh("sed -i '' 's/MAESTRO_TESTS_REVENUECAT_API_KEY/'\"$RC_E2E_TEST_API_KEY_PRODUCTION_TEST_STORE\"'/g' www/js/app.js") - sh("gem install cocoapods --no-document") sh("cordova platform add ios 2>/dev/null || true") - sh("cordova build ios --emulator") + Bundler.with_unbundled_env do + sh("gem install cocoapods --no-document") + sh("cordova build ios --emulator") + end sh("xcrun simctl install booted platforms/ios/build/emulator/MaestroTestApp.app") end sh("mkdir -p test_output") From 28cb8a6ebe021be4b71d3caaef755f75f17792b6 Mon Sep 17 00:00:00 2001 From: Antonio Pallares Date: Tue, 31 Mar 2026 00:19:33 +0200 Subject: [PATCH 14/22] Add error handling to Cordova test app for debugging Wrap deviceready handler in try/catch to show init errors on screen. Add timeout fallback to show 'Test Cases' if deviceready never fires, which helps diagnose whether the Cordova native bridge is working. Made-with: Cursor --- e2e-tests/MaestroTestApp/www/js/app.js | 41 +++++++++++++++++++------- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/e2e-tests/MaestroTestApp/www/js/app.js b/e2e-tests/MaestroTestApp/www/js/app.js index 09fa0736..00cb0303 100644 --- a/e2e-tests/MaestroTestApp/www/js/app.js +++ b/e2e-tests/MaestroTestApp/www/js/app.js @@ -1,18 +1,37 @@ +var deviceReady = false; + document.addEventListener('deviceready', function() { - Purchases.setLogLevel(Purchases.LOG_LEVEL.DEBUG); - Purchases.configure('MAESTRO_TESTS_REVENUECAT_API_KEY'); - - window.addEventListener('onCustomerInfoUpdated', function(info) { - var hasPro = info.entitlements.active && info.entitlements.active['pro'] !== undefined; - var label = document.getElementById('entitlements-label'); - if (label) { - label.textContent = 'Entitlements: ' + (hasPro ? 'pro' : 'none'); - } - }); + deviceReady = true; + try { + Purchases.setLogLevel(Purchases.LOG_LEVEL.DEBUG); + Purchases.configure('MAESTRO_TESTS_REVENUECAT_API_KEY'); - showTestCases(); + window.addEventListener('onCustomerInfoUpdated', function(info) { + var hasPro = info.entitlements.active && info.entitlements.active['pro'] !== undefined; + var label = document.getElementById('entitlements-label'); + if (label) { + label.textContent = 'Entitlements: ' + (hasPro ? 'pro' : 'none'); + } + }); + + showTestCases(); + } catch (e) { + document.getElementById('app').innerHTML = + '

Test Cases

' + + '

Init error: ' + e.message + '

' + + ''; + } }, false); +setTimeout(function() { + if (!deviceReady) { + document.getElementById('app').innerHTML = + '

Test Cases

' + + '

Warning: deviceready did not fire

' + + ''; + } +}, 10000); + function showTestCases() { document.getElementById('app').innerHTML = '

Test Cases

' + From 3e48e9ee045e61d5a510b6c5ba1a2d58feb06244 Mon Sep 17 00:00:00 2001 From: Antonio Pallares Date: Tue, 31 Mar 2026 00:33:23 +0200 Subject: [PATCH 15/22] Fix Cordova iOS simulator target and Android Maestro test flow iOS: cordova build was defaulting to "iPhone 16 Plus" which doesn't exist in the iOS 26.2 runtime. Explicitly target "iPhone 17". Android: The Maestro test expected native Paywall V2 UI ("Paywall V2", "Yearly", "Continue") but Cordova uses a custom JS paywall with "Premium Access" and "Subscribe". Updated test to match actual UI. Made-with: Cursor --- .../e2e_tests/purchase_through_paywall.yaml | 15 +++++++++++---- fastlane/Fastfile | 2 +- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/e2e-tests/maestro/e2e_tests/purchase_through_paywall.yaml b/e2e-tests/maestro/e2e_tests/purchase_through_paywall.yaml index 2425182e..fd27390c 100644 --- a/e2e-tests/maestro/e2e_tests/purchase_through_paywall.yaml +++ b/e2e-tests/maestro/e2e_tests/purchase_through_paywall.yaml @@ -1,6 +1,9 @@ # This flow tests the purchase through paywall flow. # It navigates to the purchase screen, verifies initial entitlements, # presents the paywall, makes a purchase, and verifies entitlements update. +# +# Note: Cordova uses a custom JS paywall (not native Paywall V2), +# so the UI elements differ from other SDKs. appId: com.revenuecat.automatedsdktests name: Purchase through paywall @@ -20,18 +23,22 @@ name: Purchase through paywall - extendedWaitUntil: visible: "Entitlements: none" timeout: 15000 +- takeScreenshot: purchase_through_paywall - Purchase screen - assertVisible: "Entitlements: none" - assertVisible: "Present Paywall" - tapOn: text: "Present Paywall" -- assertVisible: "Paywall V2" -- tapOn: - text: "Yearly" +- extendedWaitUntil: + visible: "Premium Access" + timeout: 15000 +- takeScreenshot: purchase_through_paywall - Paywall +- assertVisible: "Premium Access" - tapOn: - text: "Continue" + text: "Subscribe" - runFlow: file: ../utils/confirm_purchase.yaml - extendedWaitUntil: visible: "Entitlements: pro" timeout: 15000 +- takeScreenshot: purchase_through_paywall - Entitlements unlocked - assertVisible: "Entitlements: pro" diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 60190568..5d8d531e 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -145,7 +145,7 @@ lane :run_maestro_e2e_tests_ios do sh("cordova platform add ios 2>/dev/null || true") Bundler.with_unbundled_env do sh("gem install cocoapods --no-document") - sh("cordova build ios --emulator") + sh("cordova build ios --emulator --target='iPhone 17'") end sh("xcrun simctl install booted platforms/ios/build/emulator/MaestroTestApp.app") end From 1cda646a823268bf3c8314466943da35a8171da2 Mon Sep 17 00:00:00 2001 From: Antonio Pallares Date: Tue, 31 Mar 2026 00:44:21 +0200 Subject: [PATCH 16/22] Fix Cordova iOS build and improve Android error diagnostics iOS: Replace cordova build with manual xcodebuild to control the simulator destination (iPhone 17). cordova build defaults to iPhone 16 Plus which doesn't exist in iOS 26.2. Android: Add error messages when Purchases SDK is unavailable or offerings are empty. Add screenshot after tapping Present Paywall to capture state before assertion. Increase timeout to 30s. Made-with: Cursor --- e2e-tests/MaestroTestApp/www/js/app.js | 6 ++++++ e2e-tests/maestro/e2e_tests/purchase_through_paywall.yaml | 3 ++- fastlane/Fastfile | 4 +++- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/e2e-tests/MaestroTestApp/www/js/app.js b/e2e-tests/MaestroTestApp/www/js/app.js index 00cb0303..08ac2ba8 100644 --- a/e2e-tests/MaestroTestApp/www/js/app.js +++ b/e2e-tests/MaestroTestApp/www/js/app.js @@ -76,11 +76,17 @@ function clearError() { function presentPaywall() { clearError(); + if (typeof Purchases === 'undefined') { + showError('Purchases SDK not available (deviceready may not have fired)'); + return; + } Purchases.getOfferings( function(offerings) { if (offerings.current && offerings.current.availablePackages.length > 0) { var pkg = offerings.current.availablePackages[0]; showPaywallOverlay(pkg); + } else { + showError('No offerings available (current: ' + JSON.stringify(offerings.current) + ')'); } }, function(error) { diff --git a/e2e-tests/maestro/e2e_tests/purchase_through_paywall.yaml b/e2e-tests/maestro/e2e_tests/purchase_through_paywall.yaml index fd27390c..f4c143aa 100644 --- a/e2e-tests/maestro/e2e_tests/purchase_through_paywall.yaml +++ b/e2e-tests/maestro/e2e_tests/purchase_through_paywall.yaml @@ -28,9 +28,10 @@ name: Purchase through paywall - assertVisible: "Present Paywall" - tapOn: text: "Present Paywall" +- takeScreenshot: purchase_through_paywall - After tap Present Paywall - extendedWaitUntil: visible: "Premium Access" - timeout: 15000 + timeout: 30000 - takeScreenshot: purchase_through_paywall - Paywall - assertVisible: "Premium Access" - tapOn: diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 5d8d531e..49a4e51a 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -145,7 +145,9 @@ lane :run_maestro_e2e_tests_ios do sh("cordova platform add ios 2>/dev/null || true") Bundler.with_unbundled_env do sh("gem install cocoapods --no-document") - sh("cordova build ios --emulator --target='iPhone 17'") + sh("cordova prepare ios") + sh("cd platforms/ios && pod install --repo-update") + sh("xcodebuild -workspace platforms/ios/App.xcworkspace -scheme App -configuration Debug -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 17' build CONFIGURATION_BUILD_DIR=$(pwd)/platforms/ios/build/emulator") end sh("xcrun simctl install booted platforms/ios/build/emulator/MaestroTestApp.app") end From 6c6c3cfdb0f70366444ccbccd76a723eec091997 Mon Sep 17 00:00:00 2001 From: Antonio Pallares Date: Tue, 31 Mar 2026 00:59:59 +0200 Subject: [PATCH 17/22] Fix Cordova iOS build: pass destination override via Cordova CLI cordova prepare fails due to project name mismatch (App.xcodeproj vs MaestroTestApp.xcodeproj). Revert to cordova build and use the -- separator to pass xcodebuild -destination flag targeting iPhone 17. Made-with: Cursor --- fastlane/Fastfile | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 49a4e51a..8e0933c8 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -145,9 +145,7 @@ lane :run_maestro_e2e_tests_ios do sh("cordova platform add ios 2>/dev/null || true") Bundler.with_unbundled_env do sh("gem install cocoapods --no-document") - sh("cordova prepare ios") - sh("cd platforms/ios && pod install --repo-update") - sh("xcodebuild -workspace platforms/ios/App.xcworkspace -scheme App -configuration Debug -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 17' build CONFIGURATION_BUILD_DIR=$(pwd)/platforms/ios/build/emulator") + sh("cordova build ios --emulator -- -destination 'platform=iOS Simulator,name=iPhone 17'") end sh("xcrun simctl install booted platforms/ios/build/emulator/MaestroTestApp.app") end From 4902c0cba77d577af59c4bfbf60ff58b08b0ac1c Mon Sep 17 00:00:00 2001 From: Antonio Pallares Date: Tue, 31 Mar 2026 01:11:51 +0200 Subject: [PATCH 18/22] Fix Cordova iOS build: bypass cordova CLI, use xcodebuild directly The iOS platform directory is fully checked into git, so we can skip cordova build entirely. Copy www/ assets into the platform dir, run pod install, and build with xcodebuild targeting iPhone 17 simulator. This avoids the simulator device selection issue where cordova defaulted to iPhone 16 Plus which is not available on Xcode 26.3. Made-with: Cursor --- fastlane/Fastfile | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 8e0933c8..48750d02 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -142,12 +142,13 @@ desc "Run maestro E2E tests on iOS" lane :run_maestro_e2e_tests_ios do Dir.chdir("../e2e-tests/MaestroTestApp") do sh("sed -i '' 's/MAESTRO_TESTS_REVENUECAT_API_KEY/'\"$RC_E2E_TEST_API_KEY_PRODUCTION_TEST_STORE\"'/g' www/js/app.js") - sh("cordova platform add ios 2>/dev/null || true") + sh("cp -R www platforms/ios/www") Bundler.with_unbundled_env do sh("gem install cocoapods --no-document") - sh("cordova build ios --emulator -- -destination 'platform=iOS Simulator,name=iPhone 17'") + sh("cd platforms/ios && pod install --repo-update") end - sh("xcrun simctl install booted platforms/ios/build/emulator/MaestroTestApp.app") + sh("xcodebuild -workspace platforms/ios/App.xcworkspace -scheme App -configuration Debug -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 17' build CONFIGURATION_BUILD_DIR=#{Dir.pwd}/build/emulator") + sh("xcrun simctl install booted build/emulator/MaestroTestApp.app") end sh("mkdir -p test_output") sh("maestro test --format junit --output test_output/report.xml --test-output-dir test_output ../e2e-tests/maestro/") From 2e06190a1c30c08041ea83f4c2243c29c876c282 Mon Sep 17 00:00:00 2001 From: Antonio Pallares Date: Tue, 31 Mar 2026 01:31:26 +0200 Subject: [PATCH 19/22] Fix Cordova Maestro tests: bundle ID mismatch, www copy, and plugin install Three fixes: 1. iOS: Update bundle ID from com.revenuecat.maestro.e2e to com.revenuecat.automatedsdktests to match Maestro test appId 2. iOS: Use rsync instead of cp -R to copy www/ contents into the platform dir without creating a nested www/www/ directory, while preserving cordova.js and cordova_plugins.js 3. Android: Explicitly install plugin from local repo with 'cordova plugin add ../../ --force' to ensure the Purchases JS bridge is properly linked Made-with: Cursor --- .../platforms/ios/App.xcodeproj/project.pbxproj | 4 ++-- e2e-tests/MaestroTestApp/platforms/ios/App/config.xml | 2 +- fastlane/Fastfile | 3 ++- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/e2e-tests/MaestroTestApp/platforms/ios/App.xcodeproj/project.pbxproj b/e2e-tests/MaestroTestApp/platforms/ios/App.xcodeproj/project.pbxproj index 80104154..65c8ed76 100644 --- a/e2e-tests/MaestroTestApp/platforms/ios/App.xcodeproj/project.pbxproj +++ b/e2e-tests/MaestroTestApp/platforms/ios/App.xcodeproj/project.pbxproj @@ -524,7 +524,7 @@ "$(inherited)", "-ObjC", ); - PRODUCT_BUNDLE_IDENTIFIER = "com.revenuecat.maestro.e2e"; + PRODUCT_BUNDLE_IDENTIFIER = "com.revenuecat.automatedsdktests"; PRODUCT_NAME = "MaestroTestApp"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator"; SUPPORTS_MACCATALYST = YES; @@ -563,7 +563,7 @@ "$(inherited)", "-ObjC", ); - PRODUCT_BUNDLE_IDENTIFIER = "com.revenuecat.maestro.e2e"; + PRODUCT_BUNDLE_IDENTIFIER = "com.revenuecat.automatedsdktests"; PRODUCT_NAME = "MaestroTestApp"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator"; SUPPORTS_MACCATALYST = YES; diff --git a/e2e-tests/MaestroTestApp/platforms/ios/App/config.xml b/e2e-tests/MaestroTestApp/platforms/ios/App/config.xml index 09797200..7c9d85ce 100644 --- a/e2e-tests/MaestroTestApp/platforms/ios/App/config.xml +++ b/e2e-tests/MaestroTestApp/platforms/ios/App/config.xml @@ -1,5 +1,5 @@ - + diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 48750d02..9b4eea32 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -142,7 +142,7 @@ desc "Run maestro E2E tests on iOS" lane :run_maestro_e2e_tests_ios do Dir.chdir("../e2e-tests/MaestroTestApp") do sh("sed -i '' 's/MAESTRO_TESTS_REVENUECAT_API_KEY/'\"$RC_E2E_TEST_API_KEY_PRODUCTION_TEST_STORE\"'/g' www/js/app.js") - sh("cp -R www platforms/ios/www") + sh("rsync -a --exclude cordova.js --exclude cordova_plugins.js --exclude plugins www/ platforms/ios/www/") Bundler.with_unbundled_env do sh("gem install cocoapods --no-document") sh("cd platforms/ios && pod install --repo-update") @@ -159,6 +159,7 @@ lane :build_maestro_app_android do Dir.chdir("../e2e-tests/MaestroTestApp") do sh("sed -i 's/MAESTRO_TESTS_REVENUECAT_API_KEY/'\"$RC_E2E_TEST_API_KEY_PRODUCTION_TEST_STORE\"'/g' www/js/app.js") sh("cordova platform add android 2>/dev/null || true") + sh("cordova plugin add ../../ --force 2>/dev/null || true") sh("cordova build android") end end From 50456815fbb04bcb9a630332dd78f1d051e91079 Mon Sep 17 00:00:00 2001 From: Antonio Pallares Date: Tue, 31 Mar 2026 01:45:36 +0200 Subject: [PATCH 20/22] Fix Cordova Android plugin install: update fetch.json source path The fetch.json pointed to a non-existent /tmp/ path from the original development setup. When cordova platform add android runs on CI, it can't resolve the plugin source. Update to use the correct relative path (../..) to the repo root. Also simplified the Android build lane to let cordova platform add handle plugin installation naturally. Made-with: Cursor --- e2e-tests/MaestroTestApp/plugins/fetch.json | 4 ++-- fastlane/Fastfile | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/e2e-tests/MaestroTestApp/plugins/fetch.json b/e2e-tests/MaestroTestApp/plugins/fetch.json index 841a4326..7e7dd5b7 100644 --- a/e2e-tests/MaestroTestApp/plugins/fetch.json +++ b/e2e-tests/MaestroTestApp/plugins/fetch.json @@ -1,8 +1,8 @@ { "cordova-plugin-purchases": { "source": { - "type": "registry", - "id": "file:/tmp/cordova-plugin-purchases-clean" + "type": "local", + "path": "../.." }, "is_top_level": true, "variables": {} diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 9b4eea32..794d0bce 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -158,8 +158,8 @@ desc "Build maestro E2E test app for Android" lane :build_maestro_app_android do Dir.chdir("../e2e-tests/MaestroTestApp") do sh("sed -i 's/MAESTRO_TESTS_REVENUECAT_API_KEY/'\"$RC_E2E_TEST_API_KEY_PRODUCTION_TEST_STORE\"'/g' www/js/app.js") - sh("cordova platform add android 2>/dev/null || true") - sh("cordova plugin add ../../ --force 2>/dev/null || true") + sh("rm -rf platforms/android") + sh("cordova platform add android") sh("cordova build android") end end From 8060b67e680d7cff0235669e0dae1bb35d04c827 Mon Sep 17 00:00:00 2001 From: Antonio Pallares Date: Tue, 31 Mar 2026 02:03:05 +0200 Subject: [PATCH 21/22] Force-track Cordova plugin www/ files that nested .gitignore excluded The plugins/cordova-plugin-purchases/ directory is a copy of the full repo, including its .gitignore which excludes www/. This causes www/plugin.js to be missing on CI, breaking cordova platform add since it can't find the JS module file to install. Made-with: Cursor --- .../cordova-plugin-purchases/www/plugin.d.ts | 1634 +++++++++++++++++ .../cordova-plugin-purchases/www/plugin.js | 1277 +++++++++++++ 2 files changed, 2911 insertions(+) create mode 100644 e2e-tests/MaestroTestApp/plugins/cordova-plugin-purchases/www/plugin.d.ts create mode 100644 e2e-tests/MaestroTestApp/plugins/cordova-plugin-purchases/www/plugin.js diff --git a/e2e-tests/MaestroTestApp/plugins/cordova-plugin-purchases/www/plugin.d.ts b/e2e-tests/MaestroTestApp/plugins/cordova-plugin-purchases/www/plugin.d.ts new file mode 100644 index 00000000..43a4a4c2 --- /dev/null +++ b/e2e-tests/MaestroTestApp/plugins/cordova-plugin-purchases/www/plugin.d.ts @@ -0,0 +1,1634 @@ +declare global { + interface Window { + cordova: any; + plugins: any; + } +} +export declare enum ATTRIBUTION_NETWORK { + APPLE_SEARCH_ADS = 0, + ADJUST = 1, + APPSFLYER = 2, + BRANCH = 3, + TENJIN = 4, + FACEBOOK = 5 +} +export declare enum PURCHASE_TYPE { + /** + * A type of SKU for in-app products. + */ + INAPP = "inapp", + /** + * A type of SKU for subscriptions. + */ + SUBS = "subs" +} +/** + * Enum for billing features. + * Currently, these are only relevant for Google Play Android users: + * https://developer.android.com/reference/com/android/billingclient/api/BillingClient.FeatureType + */ +export declare enum BILLING_FEATURE { + /** + * Purchase/query for subscriptions. + */ + SUBSCRIPTIONS = 0, + /** + * Subscriptions update/replace. + */ + SUBSCRIPTIONS_UPDATE = 1, + /** + * Purchase/query for in-app items on VR. + */ + IN_APP_ITEMS_ON_VR = 2, + /** + * Purchase/query for subscriptions on VR. + */ + SUBSCRIPTIONS_ON_VR = 3, + /** + * Launch a price change confirmation flow. + */ + PRICE_CHANGE_CONFIRMATION = 4 +} +export declare enum REFUND_REQUEST_STATUS { + /** + * Apple has received the refund request. + */ + SUCCESS = 0, + /** + * User canceled submission of the refund request. + */ + USER_CANCELLED = 1, + /** + * There was an error with the request. See message for more details. + */ + ERROR = 2 +} +export declare enum PRORATION_MODE { + UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY = 0, + /** + * Replacement takes effect immediately, and the remaining time will be + * prorated and credited to the user. This is the current default behavior. + */ + IMMEDIATE_WITH_TIME_PRORATION = 1, + /** + * Replacement takes effect immediately, and the billing cycle remains the + * same. The price for the remaining period will be charged. This option is + * only available for subscription upgrade. + */ + IMMEDIATE_AND_CHARGE_PRORATED_PRICE = 2, + /** + * Replacement takes effect immediately, and the new price will be charged on + * next recurrence time. The billing cycle stays the same. + */ + IMMEDIATE_WITHOUT_PRORATION = 3, + /** + * Replacement takes effect immediately, and the user is charged full price + * of new plan and is given a full billing cycle of subscription, + * plus remaining prorated time from the old plan. + */ + IMMEDIATE_AND_CHARGE_FULL_PRICE = 5, + /** + * Replacement takes effect when the old plan expires, and the new price will be charged at the same time. + * + * Example: Samwise's Tier 1 subscription continues until it expires on April 30. On May 1st, the + * Tier 2 subscription takes effect, and Samwise is charged $36 for his new subscription tier. + */ + DEFERRED = 6 +} +export declare enum PACKAGE_TYPE { + /** + * A package that was defined with a custom identifier. + */ + UNKNOWN = "UNKNOWN", + /** + * A package that was defined with a custom identifier. + */ + CUSTOM = "CUSTOM", + /** + * A package configured with the predefined lifetime identifier. + */ + LIFETIME = "LIFETIME", + /** + * A package configured with the predefined annual identifier. + */ + ANNUAL = "ANNUAL", + /** + * A package configured with the predefined six month identifier. + */ + SIX_MONTH = "SIX_MONTH", + /** + * A package configured with the predefined three month identifier. + */ + THREE_MONTH = "THREE_MONTH", + /** + * A package configured with the predefined two month identifier. + */ + TWO_MONTH = "TWO_MONTH", + /** + * A package configured with the predefined monthly identifier. + */ + MONTHLY = "MONTHLY", + /** + * A package configured with the predefined weekly identifier. + */ + WEEKLY = "WEEKLY" +} +export declare enum INTRO_ELIGIBILITY_STATUS { + /** + * RevenueCat doesn't have enough information to determine eligibility. + */ + INTRO_ELIGIBILITY_STATUS_UNKNOWN = 0, + /** + * The user is not eligible for a free trial or intro pricing for this product. + */ + INTRO_ELIGIBILITY_STATUS_INELIGIBLE = 1, + /** + * The user is eligible for a free trial or intro pricing for this product. + */ + INTRO_ELIGIBILITY_STATUS_ELIGIBLE = 2, + /** + * There is no free trial or intro pricing for this product. + */ + INTRO_ELIGIBILITY_STATUS_NO_INTRO_OFFER_EXISTS = 3 +} +export declare enum LOG_LEVEL { + VERBOSE = "VERBOSE", + DEBUG = "DEBUG", + INFO = "INFO", + WARN = "WARN", + ERROR = "ERROR" +} +/** + * Enum for in-app message types. + * This can be used if you disable automatic in-app message from showing automatically. + * Then, you can pass what type of messages you want to show in the `showInAppMessages` + * method in Purchases. + */ +export declare enum IN_APP_MESSAGE_TYPE { + /** + * In-app messages to indicate there has been a billing issue charging the user. + */ + BILLING_ISSUE = 0, + /** + * iOS-only. This message will show if you increase the price of a subscription and + * the user needs to opt-in to the increase. + */ + PRICE_INCREASE_CONSENT = 1, + /** + * iOS-only. StoreKit generic messages. + */ + GENERIC = 2, + /** + * iOS-only. This message will show if the subscriber is eligible for an iOS win-back + * offer and will allow the subscriber to redeem the offer. + */ + WIN_BACK_OFFER = 3 +} +/** + * The EntitlementInfo object gives you access to all of the information about the status of a user entitlement. + */ +export interface PurchasesEntitlementInfo { + /** + * The entitlement identifier configured in the RevenueCat dashboard + */ + readonly identifier: string; + /** + * True if the user has access to this entitlement + */ + readonly isActive: boolean; + /** + * True if the underlying subscription is set to renew at the end of the billing period (expirationDate). + */ + readonly willRenew: boolean; + /** + * The last period type this entitlement was in. Either: NORMAL, INTRO, TRIAL, PREPAID. + */ + readonly periodType: string; + /** + * The latest purchase or renewal date for the entitlement. + */ + readonly latestPurchaseDate: string; + /** + * The first date this entitlement was purchased. + */ + readonly originalPurchaseDate: string; + /** + * The expiration date for the entitlement, can be `null` for lifetime access. If the `periodType` is `trial`, + * this is the trial expiration date. + */ + readonly expirationDate: string | null; + /** + * The store where this entitlement was unlocked from. Either: appStore, macAppStore, playStore, stripe, + * promotional, unknownStore, amazon, rcBilling, external, paddle, testStore + */ + readonly store: string; + /** + * The product identifier that unlocked this entitlement + */ + readonly productIdentifier: string; + /** + * False if this entitlement is unlocked via a production purchase + */ + readonly isSandbox: boolean; + /** + * The date an unsubscribe was detected. Can be `null`. + * + * @note: Entitlement may still be active even if user has unsubscribed. Check the `isActive` property. + */ + readonly unsubscribeDetectedAt: string | null; + /** + * The date a billing issue was detected. Can be `null` if there is no billing issue or an issue has been resolved + * + * @note: Entitlement may still be active even if there is a billing issue. Check the `isActive` property. + */ + readonly billingIssueDetectedAt: string | null; +} +/** + * Contains all the entitlements associated to the user. + */ +export interface PurchasesEntitlementInfos { + /** + * Map of all EntitlementInfo (`PurchasesEntitlementInfo`) objects (active and inactive) keyed by entitlement identifier. + */ + readonly all: { + [key: string]: PurchasesEntitlementInfo; + }; + /** + * Map of active EntitlementInfo (`PurchasesEntitlementInfo`) objects keyed by entitlement identifier. + */ + readonly active: { + [key: string]: PurchasesEntitlementInfo; + }; + /** + * Dictionary of active ``EntitlementInfo`` objects keyed by their identifiers. + * @ Note: When queried from the sandbox environment, it only returns entitlements active in sandbox. + * When queried from production, this only returns entitlements active in production. + */ + readonly activeInCurrentEnvironment: { + [key: string]: PurchasesEntitlementInfo; + }; + /** + * Dictionary of active ``EntitlementInfo`` objects keyed by their identifiers. + * @note: these can be active on any environment. + */ + readonly activeInAnyEnvironment: { + [key: string]: PurchasesEntitlementInfo; + }; +} +export interface PurchasesStoreTransaction { + /** + * RevenueCat Id associated to the transaction. + */ + readonly transactionIdentifier: string; + /** + * Product Id associated with the transaction. + */ + readonly productIdentifier: string; + /** + * Purchase date of the transaction in ISO 8601 format. + */ + readonly purchaseDate: string; +} +export interface CustomerInfo { + /** + * Entitlements attached to this customer info + */ + readonly entitlements: PurchasesEntitlementInfos; + /** + * Set of active subscription skus + */ + readonly activeSubscriptions: [string]; + /** + * Set of purchased skus, active and inactive + */ + readonly allPurchasedProductIdentifiers: [string]; + /** + * Returns all the non-subscription purchases a user has made. + * The purchases are ordered by purchase date in ascending order. + */ + readonly nonSubscriptionTransactions: PurchasesStoreTransaction[]; + /** + * The latest expiration date of all purchased skus + */ + readonly latestExpirationDate: string | null; + /** + * The date this user was first seen in RevenueCat. + */ + readonly firstSeen: string; + /** + * The original App User Id recorded for this user. + */ + readonly originalAppUserId: string; + /** + * Date when this info was requested + */ + readonly requestDate: string; + /** + * Map of skus to expiration dates + */ + readonly allExpirationDates: { + [key: string]: string | null; + }; + /** + * Map of skus to purchase dates + */ + readonly allPurchaseDates: { + [key: string]: string | null; + }; + /** + * Returns the version number for the version of the application when the + * user bought the app. Use this for grandfathering users when migrating + * to subscriptions. + * + * This corresponds to the value of CFBundleVersion (in iOS) in the + * Info.plist file when the purchase was originally made. This is always null + * in Android + */ + readonly originalApplicationVersion: string | null; + /** + * Returns the purchase date for the version of the application when the user bought the app. + * Use this for grandfathering users when migrating to subscriptions. + */ + readonly originalPurchaseDate: string | null; + /** + * URL to manage the active subscription of the user. If this user has an active iOS + * subscription, this will point to the App Store, if the user has an active Play Store subscription + * it will point there. If there are no active subscriptions it will be null. + * If there are multiple for different platforms, it will point to the device store. + */ + readonly managementURL: string | null; +} +export interface PurchasesIntroPrice { + /** + * Price in the local currency. + */ + readonly price: number; + /** + * Formatted price, including its currency sign, such as €3.99. + */ + readonly priceString: string; + /** + * Number of subscription billing periods for which the user will be given the discount, such as 3. + */ + readonly cycles: number; + /** + * Billing period of the discount, specified in ISO 8601 format. + */ + readonly period: string; + /** + * Unit for the billing period of the discount, can be DAY, WEEK, MONTH or YEAR. + */ + readonly periodUnit: string; + /** + * Number of units for the billing period of the discount. + */ + readonly periodNumberOfUnits: number; +} +export interface PurchasesStoreProductDiscount { + /** + * Identifier of the discount. + */ + readonly identifier: string; + /** + * Price in the local currency. + */ + readonly price: number; + /** + * Formatted price, including its currency sign, such as €3.99. + */ + readonly priceString: string; + /** + * Number of subscription billing periods for which the user will be given the discount, such as 3. + */ + readonly cycles: number; + /** + * Billing period of the discount, specified in ISO 8601 format. + */ + readonly period: string; + /** + * Unit for the billing period of the discount, can be DAY, WEEK, MONTH or YEAR. + */ + readonly periodUnit: string; + /** + * Number of units for the billing period of the discount. + */ + readonly periodNumberOfUnits: number; +} +export interface PurchasesWinBackOffer extends PurchasesStoreProductDiscount { +} +export interface PurchasesStoreProduct { + /** + * Product Id. + */ + readonly identifier: string; + /** + * Description of the product. + */ + readonly description: string; + /** + * Title of the product. + */ + readonly title: string; + /** + * Price of the product in the local currency. + */ + readonly price: number; + /** + * Formatted price of the item, including its currency sign, such as €3.99. + */ + readonly priceString: string; + /** + * Currency code for price and original price. + * Contains the currency code value of defaultOption for Google Play. + */ + readonly currencyCode: string; + /** + * Introductory price. + */ + readonly introPrice: PurchasesIntroPrice | null; + /** + * Collection of discount offers for a product. Null for Android. + */ + readonly discounts: PurchasesStoreProductDiscount[] | null; + /** + * Product category. + */ + readonly productCategory: PRODUCT_CATEGORY | null; + /** + * Subscription period, specified in ISO 8601 format. For example, + * P1W equates to one week, P1M equates to one month, + * P3M equates to three months, P6M equates to six months, + * and P1Y equates to one year. + * Note: Not available for Amazon. + */ + readonly subscriptionPeriod: string | null; + /** + * Default subscription option for a product. Google Play only. + */ + readonly defaultOption: SubscriptionOption | null; + /** + * Collection of subscription options for a product. Google Play only. + */ + readonly subscriptionOptions: SubscriptionOption[] | null; + /** + * Offering identifier the store product was presented from. + * Null if not using offerings or if fetched directly from store via getProducts. + */ + readonly presentedOfferingIdentifier: string | null; +} +export declare enum PRODUCT_CATEGORY { + /** + * A type of product for non-subscription. + */ + NON_SUBSCRIPTION = "NON_SUBSCRIPTION", + /** + * A type of product for subscriptions. + */ + SUBSCRIPTION = "SUBSCRIPTION", + /** + * A type of product for unknowns. + */ + UNKNOWN = "UNKNOWN" +} +/** + * Contains information about the product available for the user to purchase. + * For more info see https://docs.revenuecat.com/docs/entitlements + */ +export interface PurchasesPackage { + /** + * Unique identifier for this package. Can be one a predefined package type or a custom one. + */ + readonly identifier: string; + /** + * Package type for the product. Will be one of [PACKAGE_TYPE]. + */ + readonly packageType: PACKAGE_TYPE; + /** + * Product assigned to this package. + */ + readonly product: PurchasesStoreProduct; + /** + * Offering this package belongs to. + */ + readonly offeringIdentifier: string; + /** + * URL to use for web checkout for this package. Null if not available. + */ + readonly webCheckoutUrl: string | null; +} +/** + * An offering is a collection of Packages (`PurchasesPackage`) available for the user to purchase. + * For more info see https://docs.revenuecat.com/docs/entitlements + */ +export interface PurchasesOffering { + /** + * Unique identifier defined in RevenueCat dashboard. + */ + readonly identifier: string; + /** + * Offering description defined in RevenueCat dashboard. + */ + readonly serverDescription: string; + /** + * Offering metadata defined in RevenueCat dashboard. To access values, you need + * to check the type beforehand. For example: + * const my_unknown_value: unknown = offering.metadata['my_key']; + * const my_string_value: string | undefined = typeof(my_unknown_value) === 'string' ? my_unknown_value : undefined; + */ + readonly metadata: { + [key: string]: unknown; + }; + /** + * Array of `Package` objects available for purchase. + */ + readonly availablePackages: PurchasesPackage[]; + /** + * Lifetime package type configured in the RevenueCat dashboard, if available. + */ + readonly lifetime: PurchasesPackage | null; + /** + * Annual package type configured in the RevenueCat dashboard, if available. + */ + readonly annual: PurchasesPackage | null; + /** + * Six month package type configured in the RevenueCat dashboard, if available. + */ + readonly sixMonth: PurchasesPackage | null; + /** + * Three month package type configured in the RevenueCat dashboard, if available. + */ + readonly threeMonth: PurchasesPackage | null; + /** + * Two month package type configured in the RevenueCat dashboard, if available. + */ + readonly twoMonth: PurchasesPackage | null; + /** + * Monthly package type configured in the RevenueCat dashboard, if available. + */ + readonly monthly: PurchasesPackage | null; + /** + * Weekly package type configured in the RevenueCat dashboard, if available. + */ + readonly weekly: PurchasesPackage | null; + /** + * URL to use for web checkout for this offering. Null if not available. + */ + readonly webCheckoutUrl: string | null; +} +/** + * Contains all the offerings configured in RevenueCat dashboard. + * For more info see https://docs.revenuecat.com/docs/entitlements + */ +export interface PurchasesOfferings { + /** + * Map of all Offerings [PurchasesOffering] objects keyed by their identifier. + */ + readonly all: { + [key: string]: PurchasesOffering; + }; + /** + * Current offering configured in the RevenueCat dashboard. + */ + readonly current: PurchasesOffering | null; +} +export interface PurchasesError { + code: number; + message: string; + readableErrorCode: string; + underlyingErrorMessage?: string; +} +/** + * Holds the information used when upgrading from another sku. For Android use only. + */ +export interface UpgradeInfo { + /** + * The oldSKU to upgrade from. + */ + readonly oldSKU: string; + /** + * The [PRORATION_MODE] to use when upgrading the given oldSKU. + */ + readonly prorationMode?: PRORATION_MODE; +} +/** + * Holds the information used when upgrading from another sku. For Android use only. + */ +export interface GoogleProductChangeInfo { + /** + * The old product identifier to upgrade from. + */ + readonly oldProductIdentifier: string; + /** + * The [PRORATION_MODE] to use when upgrading the given oldSKU. + */ + readonly prorationMode?: PRORATION_MODE; +} +/** + * Holds the introductory price status + */ +export interface IntroEligibility { + /** + * The introductory price eligibility status + */ + readonly status: INTRO_ELIGIBILITY_STATUS; + /** + * Description of the status + */ + readonly description: string; +} +/** + * Holds the logIn result + */ +export interface LogInResult { + /** + * The Customer Info for the user. + */ + readonly customerInfo: CustomerInfo; + /** + * True if the call resulted in a new user getting created in the RevenueCat backend. + */ + readonly created: boolean; +} +/** + * Defines which version of StoreKit may be used + */ +export declare enum STOREKIT_VERSION { + /** + * Always use StoreKit 1. + */ + STOREKIT_1 = "STOREKIT_1", + /** + * Always use StoreKit 2 (StoreKit 1 will be used if StoreKit 2 is not available in the current device.) + * - Warning: Make sure you have an In-App Purchase Key configured in your app. + * Please see https://rev.cat/in-app-purchase-key-configuration for more info. + */ + STOREKIT_2 = "STOREKIT_2", + /** + * Let RevenueCat use the most appropiate version of StoreKit + */ + DEFAULT = "DEFAULT" +} +/** + * Modes for completing the purchase process. + */ +export declare enum PURCHASES_ARE_COMPLETED_BY_TYPE { + /** + * RevenueCat will **not** automatically acknowledge any purchases. You will have to do so manually. + * + * **Note:** failing to acknowledge a purchase within 3 days will lead to Google Play automatically issuing a + * refund to the user. + * + * For more info, see [revenuecat.com](https://docs.revenuecat.com/docs/observer-mode#option-2-client-side). + */ + MY_APP = "MY_APP", + /** + * RevenueCat will automatically acknowledge verified purchases. No action is required by you. + */ + REVENUECAT = "REVENUECAT" +} +/** + * Configuration option that specifies that your app will complete purchases. + */ +export declare type PurchasesAreCompletedByMyApp = { + type: PURCHASES_ARE_COMPLETED_BY_TYPE.MY_APP; + /** + * The version of StoreKit that your app is using to make purchases. This value is ignored + * on Android, so if your app is Android-only, you may provide any value. + */ + storeKitVersion: STOREKIT_VERSION; +}; +/** + * Allows you to specify whether you want RevenueCat to complete your app's purchases + * or if your app will do so. + * + * You can configure RevenueCat to complete your purchases like so: + * ```typescript + * Purchases.configure({ + * apiKey: "123", + * purchasesAreCompletedBy: PURCHASES_ARE_COMPLETED_BY.REVENUECAT, + * }); + * ``` + * + * You can specify that purchase are completed by your app like so: + * ```typescript + * Purchases.configure({ + * apiKey: "123", + * purchasesAreCompletedBy: { + * type: PURCHASES_ARE_COMPLETED_BY.MY_APP, + * storeKitVersion: STOREKIT_VERSION.STOREKIT_1 + * }, + * }); + * ``` + */ +export declare type PurchasesAreCompletedBy = PURCHASES_ARE_COMPLETED_BY_TYPE.REVENUECAT | PurchasesAreCompletedByMyApp; +/** + * Holds parameters to initialize the SDK. + */ +export interface PurchasesConfiguration { + /** + * RevenueCat API Key. Needs to be a string + */ + apiKey: string; + /** + * A unique id for identifying the user + */ + appUserID?: string | null; + /** + * Set this to MY_APP and provide a STOREKIT_VERSION if you have your own IAP implementation and + * want to only use RevenueCat's backend. Defaults to PURCHASES_ARE_COMPLETED_BY_TYPE.REVENUECAT. + * + * If you are on Android and setting this to MY_APP, will have to acknowledge the purchases yourself. + * If your app is only on Android, you may specify any StoreKit version, as it is ignored by the + * Android SDK. + */ + purchasesAreCompletedBy?: PurchasesAreCompletedBy; + /** + * An optional string. iOS-only, will be ignored for Android. + * Set this if you would like the RevenueCat SDK to store its preferences in a different NSUserDefaults + * suite, otherwise it will use standardUserDefaults. Default is null, which will make the SDK use standardUserDefaults. + */ + userDefaultsSuiteName?: string; + /** + * iOS-only, will be ignored for Android. + * + * By selecting the DEFAULT value, RevenueCat will automatically select the most appropriate StoreKit version + * for the app's runtime environment. + * + * - Warning: Make sure you have an In-App Purchase Key configured in your app. + * Please see https://rev.cat/in-app-purchase-key-configuration for more info. + * + * - Note: StoreKit 2 is only available on iOS 16+. StoreKit 1 will be used for previous iOS versions + * regardless of this setting. + */ + storeKitVersion?: STOREKIT_VERSION; + /** + * An optional boolean. Android only. Required to configure the plugin to be used in the Amazon Appstore. + */ + useAmazon?: boolean; + /** + * Whether we should show store in-app messages automatically. Both Google Play and the App Store provide in-app + * messages for some situations like billing issues. By default, those messages will be shown automatically. + * This allows to disable that behavior, so you can display those messages at your convenience. For more information, + * check: https://rev.cat/storekit-message and https://rev.cat/googleplayinappmessaging + */ + shouldShowInAppMessagesAutomatically?: boolean; +} +/** + * Contains all details associated with a SubscriptionOption + * Used only for Google + */ +export interface SubscriptionOption { + /** + * Identifier of the subscription option + * If this SubscriptionOption represents a base plan, this will be the basePlanId. + * If it represents an offer, it will be {basePlanId}:{offerId} + */ + readonly id: string; + /** + * Identifier of the StoreProduct associated with this SubscriptionOption + * This will be {subId}:{basePlanId} + */ + readonly storeProductId: string; + /** + * Identifer of the subscription associated with this SubscriptionOption + * This will be {subId} + */ + readonly productId: string; + /** + * Pricing phases defining a user's payment plan for the product over time. + */ + readonly pricingPhases: PricingPhase[]; + /** + * Tags defined on the base plan or offer. Empty for Amazon. + */ + readonly tags: string[]; + /** + * True if this SubscriptionOption represents a subscription base plan (rather than an offer). + */ + readonly isBasePlan: boolean; + /** + * The subscription period of fullPricePhase (after free and intro trials). + */ + readonly billingPeriod: Period | null; + /** + * True if the subscription is pre-paid. + */ + readonly isPrepaid: boolean; + /** + * The full price PricingPhase of the subscription. + * Looks for the last price phase of the SubscriptionOption. + */ + readonly fullPricePhase: PricingPhase | null; + /** + * The free trial PricingPhase of the subscription. + * Looks for the first pricing phase of the SubscriptionOption where amountMicros is 0. + * There can be a freeTrialPhase and an introductoryPhase in the same SubscriptionOption. + */ + readonly freePhase: PricingPhase | null; + /** + * The intro trial PricingPhase of the subscription. + * Looks for the first pricing phase of the SubscriptionOption where amountMicros is greater than 0. + * There can be a freeTrialPhase and an introductoryPhase in the same SubscriptionOption. + */ + readonly introPhase: PricingPhase | null; + /** + * Offering identifier the subscription option was presented from + */ + readonly presentedOfferingIdentifier: string | null; +} +/** + * Contains all the details associated with a PricingPhase + */ +export interface PricingPhase { + /** + * Billing period for which the PricingPhase applies + */ + readonly billingPeriod: Period; + /** + * Recurrence mode of the PricingPhase + */ + readonly recurrenceMode: RECURRENCE_MODE | null; + /** + * Number of cycles for which the pricing phase applies. + * Null for infiniteRecurring or finiteRecurring recurrence modes. + */ + readonly billingCycleCount: number | null; + /** + * Price of the PricingPhase + */ + readonly price: Price; + /** + * Indicates how the pricing phase is charged for finiteRecurring pricing phases + */ + readonly offerPaymentMode: OFFER_PAYMENT_MODE | null; +} +/** + * Recurrence mode for a pricing phase + */ +export declare enum RECURRENCE_MODE { + /** + * Pricing phase repeats infinitely until cancellation + */ + INFINITE_RECURRING = 1, + /** + * Pricing phase repeats for a fixed number of billing periods + */ + FINITE_RECURRING = 2, + /** + * Pricing phase does not repeat + */ + NON_RECURRING = 3 +} +/** + * Payment mode for offer pricing phases. Google Play only. + */ +export declare enum OFFER_PAYMENT_MODE { + /** + * Subscribers don't pay until the specified period ends + */ + FREE_TRIAL = "FREE_TRIAL", + /** + * Subscribers pay up front for a specified period + */ + SINGLE_PAYMENT = "SINGLE_PAYMENT", + /** + * Subscribers pay a discounted amount for a specified number of periods + */ + DISCOUNTED_RECURRING_PAYMENT = "DISCOUNTED_RECURRING_PAYMENT" +} +/** + * Contains all the details associated with a Price + */ +export interface Price { + /** + * Formatted price of the item, including its currency sign. For example $3.00 + */ + readonly formatted: string; + /** + * Price in micro-units, where 1,000,000 micro-units equal one unit of the currency. + * + * For example, if price is "€7.99", price_amount_micros is 7,990,000. This value represents + * the localized, rounded price for a particular currency. + */ + readonly amountMicros: number; + /** + * Returns ISO 4217 currency code for price and original price. + * + * For example, if price is specified in British pounds sterling, price_currency_code is "GBP". + * If currency code cannot be determined, currency symbol is returned. + */ + readonly currencyCode: string; +} +/** + * Contains all the details associated with a Period + */ +export interface Period { + /** + * The number of period units: day, week, month, year, unknown + */ + readonly unit: PERIOD_UNIT; + /** + * The increment of time that a subscription period is specified in + */ + readonly value: number; + /** + * Specified in ISO 8601 format. For example, P1W equates to one week, + * P1M equates to one month, P3M equates to three months, P6M equates to six months, + * and P1Y equates to one year + */ + readonly iso8601: string; +} +/** + * Time duration unit for Period. + */ +export declare enum PERIOD_UNIT { + DAY = "DAY", + WEEK = "WEEK", + MONTH = "MONTH", + YEAR = "YEAR", + UNKNOWN = "UNKNOWN" +} +/** + * The VirtualCurrencies object contains all the virtual currencies associated to the user. + */ +export interface PurchasesVirtualCurrencies { + /** + * Map of all `PurchasesVirtualCurrency` objects keyed by virtual currency code. + */ + readonly all: { + [key: string]: PurchasesVirtualCurrency; + }; +} +/** + * The VirtualCurrency object represents information about a virtual currency in the app. + * Use this object to access information about a virtual currency, such as its current balance. + */ +export interface PurchasesVirtualCurrency { + /** + * The virtual currency's balance. + */ + readonly balance: number; + /** + * The virtual currency's name. + */ + readonly name: string; + /** + * The virtual currency's code. + */ + readonly code: string; + /** + * The virtual currency's description defined in the RevenueCat dashboard. + */ + readonly serverDescription: string | null; +} +export declare type ShouldPurchasePromoProductListener = (deferredPurchase: () => void) => void; +export declare type LogHandler = (logLevel: LOG_LEVEL, message: string) => void; +declare class Purchases { + /** + * Enum for attribution networks + * @readonly + * @enum {Number} + */ + static ATTRIBUTION_NETWORK: typeof ATTRIBUTION_NETWORK; + /** + * Supported SKU types. + * @readonly + * @enum {string} + */ + static PURCHASE_TYPE: typeof PURCHASE_TYPE; + /** + * Supported product categories. + * @readonly + * @enum {string} + */ + static PRODUCT_CATEGORY: typeof PRODUCT_CATEGORY; + /** + * Enum for billing features. + * Currently, these are only relevant for Google Play Android users: + * https://developer.android.com/reference/com/android/billingclient/api/BillingClient.FeatureType + */ + static BILLING_FEATURE: typeof BILLING_FEATURE; + /** + * Enum with possible return states for beginning refund request. + * @readonly + * @enum {string} + */ + static REFUND_REQUEST_STATUS: typeof REFUND_REQUEST_STATUS; + /** + * Replace SKU's ProrationMode. + * @readonly + * @enum {number} + */ + static PRORATION_MODE: typeof PRORATION_MODE; + /** + * Enumeration of all possible Package types. + * @readonly + * @enum {string} + */ + static PACKAGE_TYPE: typeof PACKAGE_TYPE; + /** + * Enum of different possible states for intro price eligibility status. + * @readonly + * @enum {number} + */ + static INTRO_ELIGIBILITY_STATUS: typeof INTRO_ELIGIBILITY_STATUS; + /** + * Enum of different possible log levels. + * @readonly + * @enum {string} + */ + static LOG_LEVEL: typeof LOG_LEVEL; + /** + * Enum of different possible in-app message types. + * @readonly + * @enum {string} + */ + static IN_APP_MESSAGE_TYPE: typeof IN_APP_MESSAGE_TYPE; + /** + * Modes for completing the purchase process. + * @readonly + * @enum {string} + */ + static PURCHASES_ARE_COMPLETED_BY_TYPE: typeof PURCHASES_ARE_COMPLETED_BY_TYPE; + /** + * Defines which version of StoreKit may be used. + * @readonly + * @enum {string} + */ + static STOREKIT_VERSION: typeof STOREKIT_VERSION; + /** + * @deprecated Use {@link configureWith} instead. It accepts a {@link PurchasesConfiguration} object which offers more flexibility. + * + * Sets up Purchases with your API key and an app user id. + * @param {string} apiKey RevenueCat API Key. Needs to be a string + * @param {string?} appUserID A unique id for identifying the user + * @param {string?} userDefaultsSuiteName An optional string. iOS-only, will be ignored for Android. + * Set this if you would like the RevenueCat SDK to store its preferences in a different NSUserDefaults + * suite, otherwise it will use standardUserDefaults. Default is null, which will make the SDK use standardUserDefaults. + */ + static configure(apiKey: string, appUserID?: string | null, userDefaultsSuiteName?: string): void; + /** + * Sets up Purchases with your API key and an app user id. + * @param {PurchasesConfiguration} Object containing configuration parameters + */ + static configureWith({ apiKey, appUserID, purchasesAreCompletedBy, userDefaultsSuiteName, storeKitVersion, useAmazon, shouldShowInAppMessagesAutomatically }: PurchasesConfiguration): void; + /** + * Gets the Offerings configured in the RevenueCat dashboard + * @param {function(PurchasesOfferings):void} callback Callback triggered after a successful getOfferings call. + * @param {function(PurchasesError):void} errorCallback Callback triggered after an error or when retrieving offerings. + */ + static getOfferings(callback: (offerings: PurchasesOfferings) => void, errorCallback: (error: PurchasesError) => void): void; + /** + * Fetch the product info + * @param {[string]} productIdentifiers Array of product identifiers + * @param {function(PurchasesStoreProduct[]):void} callback Callback triggered after a successful getProducts call. It will receive an array of product objects. + * @param {function(PurchasesError):void} errorCallback Callback triggered after an error or when retrieving products + * @param {PURCHASE_TYPE} type Optional type of products to fetch, can be inapp or subs. Subs by default + */ + static getProducts(productIdentifiers: string[], callback: (products: PurchasesStoreProduct[]) => void, errorCallback: (error: PurchasesError) => void, type?: PURCHASE_TYPE): void; + /** + * iOS 18.0+ only. Use this function to retrieve the eligible win-back offers that a subscriber + * is eligible for for a given product. + * + * @param {PurchasesStoreProduct} product The product the user intends to purchase. + * @param {function(PurchasesWinBackOffer[]):void} callback Callback triggered after a successful getEligibleWinBackOffersForProduct call. + * It will receive an array of eligible win-back objects for the provided product. + * @param {function(PurchasesError):void} errorCallback Callback triggered after an error when retrieving eligible win-back offers. + */ + static getEligibleWinBackOffersForProduct(product: PurchasesStoreProduct, callback: (winBackOffers: PurchasesWinBackOffer[]) => void, errorCallback: (error: PurchasesError) => void): Promise; + /** + * iOS 18.0+ only. Use this function to purchase a product with a win-back offer. + * Fetch eligible win-back offers with getEligibleWinBackOffersForProduct. + * + * @param {PurchasesStoreProduct} product The product the user intends to purchase. + * @param {PurchasesWinBackOffer} winBackOffer The win-back offer the user intends to purchase. + * @param {function(string, CustomerInfo):void} callback Callback triggered after a successful purchaseProductWithWinBackOffer call. + * @param {function(PurchasesError, boolean):void} errorCallback Callback triggered after an error. + */ + static purchaseProductWithWinBackOffer(product: PurchasesStoreProduct, winBackOffer: PurchasesWinBackOffer, callback: ({ productIdentifier, customerInfo, }: { + productIdentifier: string; + customerInfo: CustomerInfo; + }) => void, errorCallback: ({ error, userCancelled, }: { + error: PurchasesError; + userCancelled: boolean; + }) => void): Promise; + /** + * iOS 18.0+ only. Use this function to retrieve the eligible win-back offers that a subscriber + * is eligible for for a given package. + * + * @param {PurchasesPackage} package The package the user intends to purchase. + * @param {function(PurchasesWinBackOffer[]):void} callback Callback triggered after a successful getEligibleWinBackOffersForPackage call. + * It will receive an array of eligible win-back objects for the provided package. + * @param {function(PurchasesError):void} errorCallback Callback triggered after an error when retrieving eligible win-back offers. + */ + static getEligibleWinBackOffersForPackage(aPackage: PurchasesPackage, callback: (winBackOffers: PurchasesWinBackOffer[]) => void, errorCallback: (error: PurchasesError) => void): Promise; + /** + * iOS 18.0+ only. Use this function to purchase a product with a win-back offer. + * Fetch eligible win-back offers with getEligibleWinBackOffersForProduct. + * + * @param {PurchasesPackage} aPackage The package the user intends to purchase. + * @param {PurchasesWinBackOffer} winBackOffer The win-back offer the user intends to purchase. + * @param {function(string, CustomerInfo):void} callback Callback triggered after a successful purchasePackageWithWinBackOffer call. + * @param {function(PurchasesError, boolean):void} errorCallback Callback triggered after an error. + */ + static purchasePackageWithWinBackOffer(aPackage: PurchasesPackage, winBackOffer: PurchasesWinBackOffer, callback: ({ productIdentifier, customerInfo, }: { + productIdentifier: string; + customerInfo: CustomerInfo; + }) => void, errorCallback: ({ error, userCancelled, }: { + error: PurchasesError; + userCancelled: boolean; + }) => void): Promise; + /** + * Make a purchase + * + * @param {string} productIdentifier The product identifier of the product you want to purchase. + * @param {function(string, CustomerInfo):void} callback Callback triggered after a successful purchase. + * @param {function(PurchasesError, boolean):void} errorCallback Callback triggered after an error or when the user cancels the purchase. + * If user cancelled, userCancelled will be true + * @param {UpgradeInfo} upgradeInfo Android only. Optional UpgradeInfo you wish to upgrade from containing the oldSKU + * and the optional prorationMode. + * @param {PURCHASE_TYPE} type Optional type of product, can be inapp or subs. Subs by default + */ + static purchaseProduct(productIdentifier: string, callback: ({ productIdentifier, customerInfo, }: { + productIdentifier: string; + customerInfo: CustomerInfo; + }) => void, errorCallback: ({ error, userCancelled, }: { + error: PurchasesError; + userCancelled: boolean; + }) => void, upgradeInfo?: UpgradeInfo | null, type?: PURCHASE_TYPE): void; + /** + * Make a purchase + * + * @param {PurchasesStoreProduct} product The product you want to purchase + * @param {function(string, CustomerInfo):void} callback Callback triggered after a successful purchase. + * @param {function(PurchasesError, boolean):void} errorCallback Callback triggered after an error or when the user cancels the purchase + * If user cancelled, userCancelled will be true + * @param {GoogleProductChangeInfo} googleProductChangeInfo Android only. Optional GoogleProductChangeInfo you + * wish to upgrade from containing the oldProductIdentifier and the optional prorationMode. + * @param {boolean} googleIsPersonalizedPrice Android and Google only. Optional boolean indicates personalized pricing on products available for purchase in the EU. + * For compliance with EU regulations. User will see "This price has been customized for you" in the purchase dialog when true. + * See https://developer.android.com/google/play/billing/integrate#personalized-price for more info. + */ + static purchaseStoreProduct(product: PurchasesStoreProduct, callback: ({ productIdentifier, customerInfo, }: { + productIdentifier: string; + customerInfo: CustomerInfo; + }) => void, errorCallback: ({ error, userCancelled, }: { + error: PurchasesError; + userCancelled: boolean; + }) => void, googleProductChangeInfo?: GoogleProductChangeInfo | null, googleIsPersonalizedPrice?: boolean): void; + /** + * Make a purchase + * + * @param {PurchasesPackage} aPackage The Package you wish to purchase. You can get the Packages by calling getOfferings + * @param {function(string, CustomerInfo):void} callback Callback triggered after a successful purchase. + * @param {function(PurchasesError, boolean):void} errorCallback Callback triggered after an error or when the user cancels the purchase. + * If user cancelled, userCancelled will be true + * @param {UpgradeInfo} upgradeInfo Android only. Optional UpgradeInfo you wish to upgrade from containing the oldSKU + * and the optional prorationMode. + * @param {GoogleProductChangeInfo} googleProductChangeInfo Android only. Optional GoogleProductChangeInfo you + * @param {boolean} googleIsPersonalizedPrice Android and Google only. Optional boolean indicates personalized pricing on products available for purchase in the EU. + * For compliance with EU regulations. User will see "This price has been customized for you" in the purchase dialog when true. + * See https://developer.android.com/google/play/billing/integrate#personalized-price for more info. + */ + static purchasePackage(aPackage: PurchasesPackage, callback: ({ productIdentifier, customerInfo, }: { + productIdentifier: string; + customerInfo: CustomerInfo; + }) => void, errorCallback: ({ error, userCancelled, }: { + error: PurchasesError; + userCancelled: boolean; + }) => void, upgradeInfo?: UpgradeInfo | null, googleProductChangeInfo?: GoogleProductChangeInfo | null, googleIsPersonalizedPrice?: boolean): void; + /** + * Google only. Make a purchase of a subscriptionOption + * + * @param {SubscriptionOption} subscriptionOption The SubscriptionOption you wish to purchase. You can get the SubscriptionOption from StoreProducts by calling getOfferings + * @param {function(string, CustomerInfo):void} callback Callback triggered after a successful purchase. + * @param {function(PurchasesError, boolean):void} errorCallback Callback triggered after an error or when the user cancels the purchase. + * If user cancelled, userCancelled will be true + * @param {GoogleProductChangeInfo} googleProductChangeInfo Android only. Optional GoogleProductChangeInfo you + * wish to upgrade from containing the oldProductIdentifier and the optional prorationMode. + * @param {boolean} googleIsPersonalizedPrice Android and Google only. Optional boolean indicates personalized pricing on products available for purchase in the EU. + * For compliance with EU regulations. User will see "This price has been customized for you" in the purchase dialog when true. + * See https://developer.android.com/google/play/billing/integrate#personalized-price for more info. + */ + static purchaseSubscriptionOption(subscriptionOption: SubscriptionOption, callback: ({ productIdentifier, customerInfo, }: { + productIdentifier: string; + customerInfo: CustomerInfo; + }) => void, errorCallback: ({ error, userCancelled, }: { + error: PurchasesError; + userCancelled: boolean; + }) => void, googleProductChangeInfo?: GoogleProductChangeInfo | null, googleIsPersonalizedPrice?: boolean): void; + /** + * Restores a user's previous purchases and links their appUserIDs to any user's also using those purchases. + * @param {function(CustomerInfo):void} callback Callback that will receive the new customer info after restoring transactions. + * @param {function(PurchasesError):void} errorCallback Callback that will be triggered whenever there is any problem restoring the user transactions. This gets normally triggered if there + * is an error retrieving the new customer info for the new user or the user cancelled the restore + */ + static restorePurchases(callback: (customerInfo: CustomerInfo) => void, errorCallback: (error: PurchasesError) => void): void; + /** + * Get the appUserID that is currently in placed in the SDK + * @param {function(string):void} callback Callback that will receive the current appUserID + */ + static getAppUserID(callback: (appUserID: string) => void): void; + /** + * This function will logIn the current user with an appUserID. Typically this would be used after a log in + * to identify a user without calling configure. + * @param {String} appUserID The appUserID that should be linked to the currently user + * @param {function(LogInResult):void} callback Callback that will receive an object that contains the customerInfo after logging in, as well as a boolean indicating + * whether the user has just been created for the first time in the RevenueCat backend. + * @param {function(PurchasesError):void} errorCallback Callback that will be triggered whenever there is any problem logging in. + */ + static logIn(appUserID: string, callback: (logInResult: LogInResult) => void, errorCallback: (error: PurchasesError) => void): void; + /** + * Logs out the Purchases client clearing the saved appUserID. This will generate a random user id and save it in the cache. + * If the current user is already anonymous, this will produce a PurchasesError. + * @param {function(CustomerInfo):void} callback Callback that will receive the new customer info after resetting + * @param {function(PurchasesError):void} errorCallback Callback that will be triggered whenever there is an error when logging out. + * This could happen for example if logOut is called but the current user is anonymous. + */ + static logOut(callback: (customerInfo: CustomerInfo) => void, errorCallback: (error: PurchasesError) => void): void; + /** + * Gets the current customer info. This call will return the cached customer info unless the cache is stale, in which case, + * it will make a network call to retrieve it from the servers. + * @param {function(CustomerInfo):void} callback Callback that will receive the customer info + * @param {function(PurchasesError, boolean):void} errorCallback Callback that will be triggered whenever there is any problem retrieving the customer info + */ + static getCustomerInfo(callback: (customerInfo: CustomerInfo) => void, errorCallback: (error: PurchasesError) => void): void; + /** + * Enables/Disables debugs logs + * @param {boolean} enabled Enable or disable debug logs + * @deprecated Use {@link setLogLevel} instead. + */ + static setDebugLogsEnabled(enabled: boolean): void; + /** + * Used to set the log level. Useful for debugging issues with the lovely team @RevenueCat. + * @param {LOG_LEVEL} level the minimum log level to enable. + */ + static setLogLevel(level: LOG_LEVEL): void; + /** + * Set a custom log handler for redirecting logs to your own logging system. + * By default, this sends info, warning, and error messages. + * If you wish to receive Debug level messages, see [setLogLevel]. + * @param {LogHandler} logHandler It will get called for each log event. + * Use this function to redirect the log to your own logging system + */ + static setLogHandler(logHandler: LogHandler): void; + /** + * iOS only. + * @param {boolean} enabled Set this property to true *only* when testing the ask-to-buy / SCA purchases flow. + * More information: http://errors.rev.cat/ask-to-buy + */ + static setSimulatesAskToBuyInSandbox(enabled: boolean): void; + /** + * This method will send all the purchases to the RevenueCat backend. Call this when using your own implementation + * for subscriptions anytime a sync is needed, like after a successful purchase. + * + * @warning This function should only be called if you're not calling makePurchase. + */ + static syncPurchases(): void; + /** + * @deprecated Use {@link syncAmazonPurchase} instead. + * This method will send a purchase to the RevenueCat backend. This function should only be called if you are + * in Amazon observer mode or performing a client side migration of your current users to RevenueCat. + * + * The receipt IDs are cached if successfully posted so they are not posted more than once. + * + * @param {string} productID Product ID associated to the purchase. + * @param {string} receiptID ReceiptId that represents the Amazon purchase. + * @param {string} amazonUserID Amazon's userID. This parameter will be ignored when syncing a Google purchase. + * @param {(string|null|undefined)} isoCurrencyCode Product's currency code in ISO 4217 format. + * @param {(number|null|undefined)} price Product's price. + */ + static syncObserverModeAmazonPurchase(productID: string, receiptID: string, amazonUserID: string, isoCurrencyCode?: string | null, price?: number | null): void; + /** + * This method will send a purchase to the RevenueCat backend. This function should only be called if you are + * in Amazon observer mode or performing a client side migration of your current users to RevenueCat. + * + * The receipt IDs are cached if successfully posted so they are not posted more than once. + * + * @param {string} productID Product ID associated to the purchase. + * @param {string} receiptID ReceiptId that represents the Amazon purchase. + * @param {string} amazonUserID Amazon's userID. This parameter will be ignored when syncing a Google purchase. + * @param {(string|null|undefined)} isoCurrencyCode Product's currency code in ISO 4217 format. + * @param {(number|null|undefined)} price Product's price. + */ + static syncAmazonPurchase(productID: string, receiptID: string, amazonUserID: string, isoCurrencyCode?: string | null, price?: number | null): void; + /** + * iOS only. Always returns an error on iOS < 15. + * + * Use this method only if you already have your own IAP implementation using StoreKit 2 and want to use + * RevenueCat's backend. If you are using StoreKit 1 for your implementation, you do not need this method. + * + * You only need to use this method with *new* purchases. Subscription updates are observed automatically. + * + * Important: This should only be used if you have set PurchasesAreCompletedBy to MY_APP during SDK configuration. + * + * @warning You need to finish the transaction yourself after calling this method. + * + * @param {string} productID Product ID that was just purchased + * @returns {Promise} If there was a transacton found and handled for the provided product ID. + */ + static recordPurchase(productID: string, callback: (transaction: PurchasesStoreTransaction) => void, errorCallback: (error: PurchasesError) => void): void; + /** + * Enable automatic collection of Apple Search Ads attribution using AdServices. Disabled by default. + */ + static enableAdServicesAttributionTokenCollection(): void; + /** + * @param {function(boolean):void} callback Will be sent a boolean indicating if the `appUserID` has been generated + * by RevenueCat or not. + */ + static isAnonymous(callback: (isAnonymous: boolean) => void): void; + /** + * iOS only. Computes whether or not a user is eligible for the introductory pricing period of a given product. + * You should use this method to determine whether or not you show the user the normal product price or the + * introductory price. This also applies to trials (trials are considered a type of introductory pricing). + * + * @note Subscription groups are automatically collected for determining eligibility. If RevenueCat can't + * definitively compute the eligibility, most likely because of missing group information, it will return + * `INTRO_ELIGIBILITY_STATUS_UNKNOWN`. The best course of action on unknown status is to display the non-intro + * pricing, to not create a misleading situation. To avoid this, make sure you are testing with the latest version of + * iOS so that the subscription group can be collected by the SDK. Android always returns INTRO_ELIGIBILITY_STATUS_UNKNOWN. + * + * @param productIdentifiers Array of product identifiers for which you want to compute eligibility + * @param callback Will be sent a map of IntroEligibility per productId + */ + static checkTrialOrIntroductoryPriceEligibility(productIdentifiers: string[], callback: (map: { + [productId: string]: IntroEligibility; + }) => void): void; + /** + * Sets a function to be called on purchases initiated on the Apple App Store. This is only used in iOS. + * @param {ShouldPurchasePromoProductListener} shouldPurchasePromoProductListener Called when a user initiates a + * promotional in-app purchase from the App Store. If your app is able to handle a purchase at the current time, run + * the deferredPurchase function. If the app is not in a state to make a purchase: cache the deferredPurchase, then + * call the deferredPurchase when the app is ready to make the promotional purchase. + * If the purchase should never be made, you don't need to ever call the deferredPurchase and the app will not + * proceed with promotional purchases. + */ + static addShouldPurchasePromoProductListener(shouldPurchasePromoProductListener: ShouldPurchasePromoProductListener): void; + /** + * Removes a given ShouldPurchasePromoProductListener + * @param {ShouldPurchasePromoProductListener} listenerToRemove ShouldPurchasePromoProductListener reference of the listener to remove + * @returns {boolean} True if listener was removed, false otherwise + */ + static removeShouldPurchasePromoProductListener(listenerToRemove: ShouldPurchasePromoProductListener): boolean; + /** + * Invalidates the cache for customer information. + * + * Most apps will not need to use this method; invalidating the cache can leave your app in an invalid state. + * Refer to https://docs.revenuecat.com/docs/customer-info#section-get-user-information for more information on + * using the cache properly. + * + * This is useful for cases where customer information might have been updated outside of the + * app, like if a promotional subscription is granted through the RevenueCat dashboard. + */ + static invalidateCustomerInfoCache(): void; + /** + * iOS only. Presents a code redemption sheet, useful for redeeming offer codes + * Refer to https://docs.revenuecat.com/docs/ios-subscription-offers#offer-codes for more information on how + * to configure and use offer codes. + */ + static presentCodeRedemptionSheet(): void; + /** + * Subscriber attributes are useful for storing additional, structured information on a user. + * Since attributes are writable using a public key they should not be used for + * managing secure or sensitive information such as subscription status, coins, etc. + * + * Key names starting with "$" are reserved names used by RevenueCat. For a full list of key + * restrictions refer to our guide: https://docs.revenuecat.com/docs/subscriber-attributes + * + * @param attributes Map of attributes by key. Set the value as an empty string to delete an attribute. + */ + static setAttributes(attributes: { + [key: string]: string | null; + }): void; + /** + * Subscriber attribute associated with the email address for the user + * + * @param email Empty String or null will delete the subscriber attribute. + */ + static setEmail(email: string | null): void; + /** + * Subscriber attribute associated with the phone number for the user + * + * @param phoneNumber Empty String or null will delete the subscriber attribute. + */ + static setPhoneNumber(phoneNumber: string | null): void; + /** + * Subscriber attribute associated with the display name for the user + * + * @param displayName Empty String or null will delete the subscriber attribute. + */ + static setDisplayName(displayName: string | null): void; + /** + * Subscriber attribute associated with the push token for the user + * + * @param pushToken Empty String or null will delete the subscriber attribute. + */ + static setPushToken(pushToken: string | null): void; + /** + * Subscriber attribute associated with the Adjust Id for the user + * Required for the RevenueCat Adjust integration + * + * @param adjustID Empty String or null will delete the subscriber attribute. + */ + static setAdjustID(adjustID: string | null): void; + /** + * Subscriber attribute associated with the AppsFlyer Id for the user + * Required for the RevenueCat AppsFlyer integration + * @param appsflyerID Empty String or null will delete the subscriber attribute. + */ + static setAppsflyerID(appsflyerID: string | null): void; + /** + * Subscriber attribute associated with the Facebook SDK Anonymous Id for the user + * Recommended for the RevenueCat Facebook integration + * + * @param fbAnonymousID Empty String or null will delete the subscriber attribute. + */ + static setFBAnonymousID(fbAnonymousID: string | null): void; + /** + * Subscriber attribute associated with the mParticle Id for the user + * Recommended for the RevenueCat mParticle integration + * + * @param mparticleID Empty String or null will delete the subscriber attribute. + */ + static setMparticleID(mparticleID: string | null): void; + /** + * Subscriber attribute associated with the OneSignal Player Id for the user + * Required for the RevenueCat OneSignal integration + * + * @param onesignalID Empty String or null will delete the subscriber attribute. + */ + static setOnesignalID(onesignalID: string | null): void; + /** + * Subscriber attribute associated with the Airship Channel Id for the user + * Required for the RevenueCat Airship integration + * + * @param airshipChannelID Empty String or null will delete the subscriber attribute. + */ + static setAirshipChannelID(airshipChannelID: string | null): void; + /** + * Subscriber attribute associated with the Firebase App Instance ID for the user + * Required for the RevenueCat Firebase integration + * + * @param firebaseAppInstanceID Empty String or null will delete the subscriber attribute. + */ + static setFirebaseAppInstanceID(firebaseAppInstanceID: string | null): void; + /** + * Subscriber attribute associated with the Mixpanel Distinct ID for the user + * Required for the RevenueCat Mixpanel integration + * + * @param mixpanelDistinctID Empty String or null will delete the subscriber attribute. + */ + static setMixpanelDistinctID(mixpanelDistinctID: string | null): void; + /** + * Subscriber attribute associated with the CleverTap ID for the user + * Required for the RevenueCat CleverTap integration + * + * @param cleverTapID Empty String or null will delete the subscriber attribute. + */ + static setCleverTapID(cleverTapID: string | null): void; + /** + * Subscriber attribute associated with the install media source for the user + * + * @param mediaSource Empty String or null will delete the subscriber attribute. + */ + static setMediaSource(mediaSource: string | null): void; + /** + * Subscriber attribute associated with the install campaign for the user + * + * @param campaign Empty String or null will delete the subscriber attribute. + */ + static setCampaign(campaign: string | null): void; + /** + * Subscriber attribute associated with the install ad group for the user + * + * @param adGroup Empty String or null will delete the subscriber attribute. + */ + static setAdGroup(adGroup: string | null): void; + /** + * Subscriber attribute associated with the install ad for the user + * + * @param ad Empty String or null will delete the subscriber attribute. + */ + static setAd(ad: string | null): void; + /** + * Subscriber attribute associated with the install keyword for the user + * + * @param keyword Empty String or null will delete the subscriber attribute. + */ + static setKeyword(keyword: string | null): void; + /** + * Subscriber attribute associated with the install ad creative for the user + * + * @param creative Empty String or null will delete the subscriber attribute. + */ + static setCreative(creative: string | null): void; + /** + * Automatically collect subscriber attributes associated with the device identifiers. + * $idfa, $idfv, $ip on iOS + * $gpsAdId, $androidId, $ip on Android + */ + static collectDeviceIdentifiers(): void; + /** + * Set this property to your proxy URL before configuring Purchases *only* if you've received a proxy key value from your RevenueCat contact. + * @param url Proxy URL as a string. + */ + static setProxyURL(url: string): void; + /** + * Check if billing is supported for the current user (meaning IN-APP purchases are supported) + * and optionally, whether a list of specified feature types are supported. + * + * Note: Billing features are only relevant to Google Play Android users. + * For other stores and platforms, billing features won't be checked. + * @param {[BILLING_FEATURE]} features An array of feature types to check for support. Feature types must be one of + * [BILLING_FEATURE]. By default, is an empty list and no specific feature support will be checked. + * @param {function(boolean):void} callback Will be sent true if billing is supported, false otherwise. + * @param {function(PurchasesError):void} errorCallback Callback triggered after an error or when checking if billing + * is supported. + */ + static canMakePayments(features: BILLING_FEATURE[] | undefined, callback: (canMakePayments: boolean) => void, errorCallback: (error: PurchasesError) => void): void; + /** + * iOS 15+ only. Presents a refund request sheet in the current window scene for + * the latest transaction associated with the active entitlement. + * + * If the request was unsuccessful, no active entitlements could be found for + * the user, or multiple active entitlements were found for the user, + * the promise will return an error. + * If called in an unsupported platform (iOS < 15), an `unsupportedError` will be sent to the callback. + * + * Important: This method should only be used if your user can only have a single active entitlement at a given time. + * If a user could have more than one entitlement at a time, use `beginRefundRequestForEntitlement` instead. + * + * @param {function(REFUND_REQUEST_STATUS):void} callback REFUND_REQUEST_STATUS: The status of the refund request. + * Keep in mind the status could be REFUND_REQUEST_STATUS.USER_CANCELLED + * @param {function(PurchasesError):void} errorCallback Callback triggered after an error when beginning refund + * request for active entitlement. + */ + static beginRefundRequestForActiveEntitlement(callback: (refundRequestStatus: REFUND_REQUEST_STATUS) => void, errorCallback: (error: PurchasesError) => void): void; + /** + * iOS 15+ only. Presents a refund request sheet in the current window scene for + * the latest transaction associated with the `entitlement`. + * + * If the request was unsuccessful, the promise will return an error. + * If called in an unsupported platform (iOS < 15), an `unsupportedError` will be sent to the callback. + * + * @param entitlementInfo The entitlement to begin a refund request for. + * @param {function(REFUND_REQUEST_STATUS):void} callback REFUND_REQUEST_STATUS: The status of the refund request. + * Keep in mind the status could be REFUND_REQUEST_STATUS.USER_CANCELLED + * @param {function(PurchasesError):void} errorCallback Callback triggered after an error when beginning refund + * request for an entitlement. + */ + static beginRefundRequestForEntitlement(entitlementInfo: PurchasesEntitlementInfo, callback: (refundRequestStatus: REFUND_REQUEST_STATUS) => void, errorCallback: (error: PurchasesError) => void): void; + /** + * iOS 15+ only. Presents a refund request sheet in the current window scene for + * the latest transaction associated with the `product`. + * + * If the request was unsuccessful, the promise will return an error. + * If called in an unsupported platform (iOS < 15), an `unsupportedError` will be sent to the callback. + * + * @param storeProduct The StoreProduct to begin a refund request for. + * @param {function(REFUND_REQUEST_STATUS):void} callback REFUND_REQUEST_STATUS: The status of the refund request. + * Keep in mind the status could be REFUND_REQUEST_STATUS.USER_CANCELLED + * @param {function(PurchasesError):void} errorCallback Callback triggered after an error when beginning refund + * request for a product. + */ + static beginRefundRequestForProduct(storeProduct: PurchasesStoreProduct, callback: (refundRequestStatus: REFUND_REQUEST_STATUS) => void, errorCallback: (error: PurchasesError) => void): void; + /** + * Shows in-app messages available from the App Store or Google Play. You need to disable messages from showing + * automatically using [PurchasesConfiguration.shouldShowInAppMessagesAutomatically]. + * + * Note: In iOS, this requires version 16+. In older versions the promise will be resolved successfully + * immediately. + * + * @param messageTypes An array of message types that the stores can display inside your app. Must be one of + * [IN_APP_MESSAGE_TYPE]. By default, is undefined and all message types will be shown. + */ + static showInAppMessages(messageTypes?: IN_APP_MESSAGE_TYPE[]): void; + /** + * Fetches the virtual currencies for the current subscriber. + * + * @param {function(PurchasesVirtualCurrencies):void} callback Callback that will receive the subscriber's virtual currencies. + * @param {function(PurchasesError):void} errorCallback Callback that will be triggered whenever there is a problem retrieving the subscriber's virtual currencies. + */ + static getVirtualCurrencies(callback: (virtualCurrencies: PurchasesVirtualCurrencies) => void, errorCallback: (error: PurchasesError) => void): void; + /** + * Invalidates the cache for virtual currencies. + * + * This is useful for cases where a virtual currency's balance might have been updated + * outside of the app, like if you decreased a user's balance from the user spending a virtual currency, + * or if you increased the balance from your backend using the server APIs. + */ + static invalidateVirtualCurrenciesCache(): void; + /** + * The currently cached `PurchasesVirtualCurrencies` if one is available. + * This value will remain null until virtual currencies have been fetched at + * least once with `Purchases.getVirtualCurrencies` or an equivalent function. + * + * @param {function(PurchasesVirtualCurrencies):void} callback Callback that will be triggered with the currently cached virtual currencies for the current subscriber. + */ + static getCachedVirtualCurrencies(callback: (cachedVirtualCurrencies: PurchasesVirtualCurrencies | null) => void): any; + private static setupShouldPurchasePromoProductCallback; + private static getMakeDeferredPurchaseFunction; + private static convertIntToRefundRequestStatus; + private static isPurchasesAreCompletedByMyApp; +} +export default Purchases; diff --git a/e2e-tests/MaestroTestApp/plugins/cordova-plugin-purchases/www/plugin.js b/e2e-tests/MaestroTestApp/plugins/cordova-plugin-purchases/www/plugin.js new file mode 100644 index 00000000..f70cf141 --- /dev/null +++ b/e2e-tests/MaestroTestApp/plugins/cordova-plugin-purchases/www/plugin.js @@ -0,0 +1,1277 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (_) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +var __rest = (this && this.__rest) || function (s, e) { + var t = {}; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) + t[p] = s[p]; + if (s != null && typeof Object.getOwnPropertySymbols === "function") + for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { + if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) + t[p[i]] = s[p[i]]; + } + return t; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.PERIOD_UNIT = exports.OFFER_PAYMENT_MODE = exports.RECURRENCE_MODE = exports.PURCHASES_ARE_COMPLETED_BY_TYPE = exports.STOREKIT_VERSION = exports.PRODUCT_CATEGORY = exports.IN_APP_MESSAGE_TYPE = exports.LOG_LEVEL = exports.INTRO_ELIGIBILITY_STATUS = exports.PACKAGE_TYPE = exports.PRORATION_MODE = exports.REFUND_REQUEST_STATUS = exports.BILLING_FEATURE = exports.PURCHASE_TYPE = exports.ATTRIBUTION_NETWORK = void 0; +var PLUGIN_NAME = "PurchasesPlugin"; +var ATTRIBUTION_NETWORK; +(function (ATTRIBUTION_NETWORK) { + ATTRIBUTION_NETWORK[ATTRIBUTION_NETWORK["APPLE_SEARCH_ADS"] = 0] = "APPLE_SEARCH_ADS"; + ATTRIBUTION_NETWORK[ATTRIBUTION_NETWORK["ADJUST"] = 1] = "ADJUST"; + ATTRIBUTION_NETWORK[ATTRIBUTION_NETWORK["APPSFLYER"] = 2] = "APPSFLYER"; + ATTRIBUTION_NETWORK[ATTRIBUTION_NETWORK["BRANCH"] = 3] = "BRANCH"; + ATTRIBUTION_NETWORK[ATTRIBUTION_NETWORK["TENJIN"] = 4] = "TENJIN"; + ATTRIBUTION_NETWORK[ATTRIBUTION_NETWORK["FACEBOOK"] = 5] = "FACEBOOK"; +})(ATTRIBUTION_NETWORK = exports.ATTRIBUTION_NETWORK || (exports.ATTRIBUTION_NETWORK = {})); +var PURCHASE_TYPE; +(function (PURCHASE_TYPE) { + /** + * A type of SKU for in-app products. + */ + PURCHASE_TYPE["INAPP"] = "inapp"; + /** + * A type of SKU for subscriptions. + */ + PURCHASE_TYPE["SUBS"] = "subs"; +})(PURCHASE_TYPE = exports.PURCHASE_TYPE || (exports.PURCHASE_TYPE = {})); +/** + * Enum for billing features. + * Currently, these are only relevant for Google Play Android users: + * https://developer.android.com/reference/com/android/billingclient/api/BillingClient.FeatureType + */ +var BILLING_FEATURE; +(function (BILLING_FEATURE) { + /** + * Purchase/query for subscriptions. + */ + BILLING_FEATURE[BILLING_FEATURE["SUBSCRIPTIONS"] = 0] = "SUBSCRIPTIONS"; + /** + * Subscriptions update/replace. + */ + BILLING_FEATURE[BILLING_FEATURE["SUBSCRIPTIONS_UPDATE"] = 1] = "SUBSCRIPTIONS_UPDATE"; + /** + * Purchase/query for in-app items on VR. + */ + BILLING_FEATURE[BILLING_FEATURE["IN_APP_ITEMS_ON_VR"] = 2] = "IN_APP_ITEMS_ON_VR"; + /** + * Purchase/query for subscriptions on VR. + */ + BILLING_FEATURE[BILLING_FEATURE["SUBSCRIPTIONS_ON_VR"] = 3] = "SUBSCRIPTIONS_ON_VR"; + /** + * Launch a price change confirmation flow. + */ + BILLING_FEATURE[BILLING_FEATURE["PRICE_CHANGE_CONFIRMATION"] = 4] = "PRICE_CHANGE_CONFIRMATION"; +})(BILLING_FEATURE = exports.BILLING_FEATURE || (exports.BILLING_FEATURE = {})); +var REFUND_REQUEST_STATUS; +(function (REFUND_REQUEST_STATUS) { + /** + * Apple has received the refund request. + */ + REFUND_REQUEST_STATUS[REFUND_REQUEST_STATUS["SUCCESS"] = 0] = "SUCCESS"; + /** + * User canceled submission of the refund request. + */ + REFUND_REQUEST_STATUS[REFUND_REQUEST_STATUS["USER_CANCELLED"] = 1] = "USER_CANCELLED"; + /** + * There was an error with the request. See message for more details. + */ + REFUND_REQUEST_STATUS[REFUND_REQUEST_STATUS["ERROR"] = 2] = "ERROR"; +})(REFUND_REQUEST_STATUS = exports.REFUND_REQUEST_STATUS || (exports.REFUND_REQUEST_STATUS = {})); +var PRORATION_MODE; +(function (PRORATION_MODE) { + PRORATION_MODE[PRORATION_MODE["UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY"] = 0] = "UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY"; + /** + * Replacement takes effect immediately, and the remaining time will be + * prorated and credited to the user. This is the current default behavior. + */ + PRORATION_MODE[PRORATION_MODE["IMMEDIATE_WITH_TIME_PRORATION"] = 1] = "IMMEDIATE_WITH_TIME_PRORATION"; + /** + * Replacement takes effect immediately, and the billing cycle remains the + * same. The price for the remaining period will be charged. This option is + * only available for subscription upgrade. + */ + PRORATION_MODE[PRORATION_MODE["IMMEDIATE_AND_CHARGE_PRORATED_PRICE"] = 2] = "IMMEDIATE_AND_CHARGE_PRORATED_PRICE"; + /** + * Replacement takes effect immediately, and the new price will be charged on + * next recurrence time. The billing cycle stays the same. + */ + PRORATION_MODE[PRORATION_MODE["IMMEDIATE_WITHOUT_PRORATION"] = 3] = "IMMEDIATE_WITHOUT_PRORATION"; + /** + * Replacement takes effect immediately, and the user is charged full price + * of new plan and is given a full billing cycle of subscription, + * plus remaining prorated time from the old plan. + */ + PRORATION_MODE[PRORATION_MODE["IMMEDIATE_AND_CHARGE_FULL_PRICE"] = 5] = "IMMEDIATE_AND_CHARGE_FULL_PRICE"; + /** + * Replacement takes effect when the old plan expires, and the new price will be charged at the same time. + * + * Example: Samwise's Tier 1 subscription continues until it expires on April 30. On May 1st, the + * Tier 2 subscription takes effect, and Samwise is charged $36 for his new subscription tier. + */ + PRORATION_MODE[PRORATION_MODE["DEFERRED"] = 6] = "DEFERRED"; +})(PRORATION_MODE = exports.PRORATION_MODE || (exports.PRORATION_MODE = {})); +var PACKAGE_TYPE; +(function (PACKAGE_TYPE) { + /** + * A package that was defined with a custom identifier. + */ + PACKAGE_TYPE["UNKNOWN"] = "UNKNOWN"; + /** + * A package that was defined with a custom identifier. + */ + PACKAGE_TYPE["CUSTOM"] = "CUSTOM"; + /** + * A package configured with the predefined lifetime identifier. + */ + PACKAGE_TYPE["LIFETIME"] = "LIFETIME"; + /** + * A package configured with the predefined annual identifier. + */ + PACKAGE_TYPE["ANNUAL"] = "ANNUAL"; + /** + * A package configured with the predefined six month identifier. + */ + PACKAGE_TYPE["SIX_MONTH"] = "SIX_MONTH"; + /** + * A package configured with the predefined three month identifier. + */ + PACKAGE_TYPE["THREE_MONTH"] = "THREE_MONTH"; + /** + * A package configured with the predefined two month identifier. + */ + PACKAGE_TYPE["TWO_MONTH"] = "TWO_MONTH"; + /** + * A package configured with the predefined monthly identifier. + */ + PACKAGE_TYPE["MONTHLY"] = "MONTHLY"; + /** + * A package configured with the predefined weekly identifier. + */ + PACKAGE_TYPE["WEEKLY"] = "WEEKLY"; +})(PACKAGE_TYPE = exports.PACKAGE_TYPE || (exports.PACKAGE_TYPE = {})); +var INTRO_ELIGIBILITY_STATUS; +(function (INTRO_ELIGIBILITY_STATUS) { + /** + * RevenueCat doesn't have enough information to determine eligibility. + */ + INTRO_ELIGIBILITY_STATUS[INTRO_ELIGIBILITY_STATUS["INTRO_ELIGIBILITY_STATUS_UNKNOWN"] = 0] = "INTRO_ELIGIBILITY_STATUS_UNKNOWN"; + /** + * The user is not eligible for a free trial or intro pricing for this product. + */ + INTRO_ELIGIBILITY_STATUS[INTRO_ELIGIBILITY_STATUS["INTRO_ELIGIBILITY_STATUS_INELIGIBLE"] = 1] = "INTRO_ELIGIBILITY_STATUS_INELIGIBLE"; + /** + * The user is eligible for a free trial or intro pricing for this product. + */ + INTRO_ELIGIBILITY_STATUS[INTRO_ELIGIBILITY_STATUS["INTRO_ELIGIBILITY_STATUS_ELIGIBLE"] = 2] = "INTRO_ELIGIBILITY_STATUS_ELIGIBLE"; + /** + * There is no free trial or intro pricing for this product. + */ + INTRO_ELIGIBILITY_STATUS[INTRO_ELIGIBILITY_STATUS["INTRO_ELIGIBILITY_STATUS_NO_INTRO_OFFER_EXISTS"] = 3] = "INTRO_ELIGIBILITY_STATUS_NO_INTRO_OFFER_EXISTS"; +})(INTRO_ELIGIBILITY_STATUS = exports.INTRO_ELIGIBILITY_STATUS || (exports.INTRO_ELIGIBILITY_STATUS = {})); +var LOG_LEVEL; +(function (LOG_LEVEL) { + LOG_LEVEL["VERBOSE"] = "VERBOSE"; + LOG_LEVEL["DEBUG"] = "DEBUG"; + LOG_LEVEL["INFO"] = "INFO"; + LOG_LEVEL["WARN"] = "WARN"; + LOG_LEVEL["ERROR"] = "ERROR"; +})(LOG_LEVEL = exports.LOG_LEVEL || (exports.LOG_LEVEL = {})); +/** + * Enum for in-app message types. + * This can be used if you disable automatic in-app message from showing automatically. + * Then, you can pass what type of messages you want to show in the `showInAppMessages` + * method in Purchases. + */ +var IN_APP_MESSAGE_TYPE; +(function (IN_APP_MESSAGE_TYPE) { + // Make sure the enum values are in sync with those defined in iOS/Android + /** + * In-app messages to indicate there has been a billing issue charging the user. + */ + IN_APP_MESSAGE_TYPE[IN_APP_MESSAGE_TYPE["BILLING_ISSUE"] = 0] = "BILLING_ISSUE"; + /** + * iOS-only. This message will show if you increase the price of a subscription and + * the user needs to opt-in to the increase. + */ + IN_APP_MESSAGE_TYPE[IN_APP_MESSAGE_TYPE["PRICE_INCREASE_CONSENT"] = 1] = "PRICE_INCREASE_CONSENT"; + /** + * iOS-only. StoreKit generic messages. + */ + IN_APP_MESSAGE_TYPE[IN_APP_MESSAGE_TYPE["GENERIC"] = 2] = "GENERIC"; + /** + * iOS-only. This message will show if the subscriber is eligible for an iOS win-back + * offer and will allow the subscriber to redeem the offer. + */ + IN_APP_MESSAGE_TYPE[IN_APP_MESSAGE_TYPE["WIN_BACK_OFFER"] = 3] = "WIN_BACK_OFFER"; +})(IN_APP_MESSAGE_TYPE = exports.IN_APP_MESSAGE_TYPE || (exports.IN_APP_MESSAGE_TYPE = {})); +var PRODUCT_CATEGORY; +(function (PRODUCT_CATEGORY) { + /** + * A type of product for non-subscription. + */ + PRODUCT_CATEGORY["NON_SUBSCRIPTION"] = "NON_SUBSCRIPTION"; + /** + * A type of product for subscriptions. + */ + PRODUCT_CATEGORY["SUBSCRIPTION"] = "SUBSCRIPTION"; + /** + * A type of product for unknowns. + */ + PRODUCT_CATEGORY["UNKNOWN"] = "UNKNOWN"; +})(PRODUCT_CATEGORY = exports.PRODUCT_CATEGORY || (exports.PRODUCT_CATEGORY = {})); +/** + * Defines which version of StoreKit may be used + */ +var STOREKIT_VERSION; +(function (STOREKIT_VERSION) { + /** + * Always use StoreKit 1. + */ + STOREKIT_VERSION["STOREKIT_1"] = "STOREKIT_1"; + /** + * Always use StoreKit 2 (StoreKit 1 will be used if StoreKit 2 is not available in the current device.) + * - Warning: Make sure you have an In-App Purchase Key configured in your app. + * Please see https://rev.cat/in-app-purchase-key-configuration for more info. + */ + STOREKIT_VERSION["STOREKIT_2"] = "STOREKIT_2"; + /** + * Let RevenueCat use the most appropiate version of StoreKit + */ + STOREKIT_VERSION["DEFAULT"] = "DEFAULT"; +})(STOREKIT_VERSION = exports.STOREKIT_VERSION || (exports.STOREKIT_VERSION = {})); +/** + * Modes for completing the purchase process. + */ +var PURCHASES_ARE_COMPLETED_BY_TYPE; +(function (PURCHASES_ARE_COMPLETED_BY_TYPE) { + /** + * RevenueCat will **not** automatically acknowledge any purchases. You will have to do so manually. + * + * **Note:** failing to acknowledge a purchase within 3 days will lead to Google Play automatically issuing a + * refund to the user. + * + * For more info, see [revenuecat.com](https://docs.revenuecat.com/docs/observer-mode#option-2-client-side). + */ + PURCHASES_ARE_COMPLETED_BY_TYPE["MY_APP"] = "MY_APP"; + /** + * RevenueCat will automatically acknowledge verified purchases. No action is required by you. + */ + PURCHASES_ARE_COMPLETED_BY_TYPE["REVENUECAT"] = "REVENUECAT"; +})(PURCHASES_ARE_COMPLETED_BY_TYPE = exports.PURCHASES_ARE_COMPLETED_BY_TYPE || (exports.PURCHASES_ARE_COMPLETED_BY_TYPE = {})); +/** + * Recurrence mode for a pricing phase + */ +var RECURRENCE_MODE; +(function (RECURRENCE_MODE) { + /** + * Pricing phase repeats infinitely until cancellation + */ + RECURRENCE_MODE[RECURRENCE_MODE["INFINITE_RECURRING"] = 1] = "INFINITE_RECURRING"; + /** + * Pricing phase repeats for a fixed number of billing periods + */ + RECURRENCE_MODE[RECURRENCE_MODE["FINITE_RECURRING"] = 2] = "FINITE_RECURRING"; + /** + * Pricing phase does not repeat + */ + RECURRENCE_MODE[RECURRENCE_MODE["NON_RECURRING"] = 3] = "NON_RECURRING"; +})(RECURRENCE_MODE = exports.RECURRENCE_MODE || (exports.RECURRENCE_MODE = {})); +/** + * Payment mode for offer pricing phases. Google Play only. + */ +var OFFER_PAYMENT_MODE; +(function (OFFER_PAYMENT_MODE) { + /** + * Subscribers don't pay until the specified period ends + */ + OFFER_PAYMENT_MODE["FREE_TRIAL"] = "FREE_TRIAL"; + /** + * Subscribers pay up front for a specified period + */ + OFFER_PAYMENT_MODE["SINGLE_PAYMENT"] = "SINGLE_PAYMENT"; + /** + * Subscribers pay a discounted amount for a specified number of periods + */ + OFFER_PAYMENT_MODE["DISCOUNTED_RECURRING_PAYMENT"] = "DISCOUNTED_RECURRING_PAYMENT"; +})(OFFER_PAYMENT_MODE = exports.OFFER_PAYMENT_MODE || (exports.OFFER_PAYMENT_MODE = {})); +/** + * Time duration unit for Period. + */ +var PERIOD_UNIT; +(function (PERIOD_UNIT) { + PERIOD_UNIT["DAY"] = "DAY"; + PERIOD_UNIT["WEEK"] = "WEEK"; + PERIOD_UNIT["MONTH"] = "MONTH"; + PERIOD_UNIT["YEAR"] = "YEAR"; + PERIOD_UNIT["UNKNOWN"] = "UNKNOWN"; +})(PERIOD_UNIT = exports.PERIOD_UNIT || (exports.PERIOD_UNIT = {})); +var shouldPurchasePromoProductListeners = []; +var Purchases = /** @class */ (function () { + function Purchases() { + } + /** + * @deprecated Use {@link configureWith} instead. It accepts a {@link PurchasesConfiguration} object which offers more flexibility. + * + * Sets up Purchases with your API key and an app user id. + * @param {string} apiKey RevenueCat API Key. Needs to be a string + * @param {string?} appUserID A unique id for identifying the user + * @param {string?} userDefaultsSuiteName An optional string. iOS-only, will be ignored for Android. + * Set this if you would like the RevenueCat SDK to store its preferences in a different NSUserDefaults + * suite, otherwise it will use standardUserDefaults. Default is null, which will make the SDK use standardUserDefaults. + */ + Purchases.configure = function (apiKey, appUserID, userDefaultsSuiteName) { + this.configureWith({ + apiKey: apiKey, + appUserID: appUserID, + userDefaultsSuiteName: userDefaultsSuiteName, + useAmazon: false + }); + }; + /** + * Sets up Purchases with your API key and an app user id. + * @param {PurchasesConfiguration} Object containing configuration parameters + */ + Purchases.configureWith = function (_a) { + var apiKey = _a.apiKey, _b = _a.appUserID, appUserID = _b === void 0 ? null : _b, purchasesAreCompletedBy = _a.purchasesAreCompletedBy, userDefaultsSuiteName = _a.userDefaultsSuiteName, storeKitVersion = _a.storeKitVersion, _c = _a.useAmazon, useAmazon = _c === void 0 ? false : _c, _d = _a.shouldShowInAppMessagesAutomatically, shouldShowInAppMessagesAutomatically = _d === void 0 ? true : _d; + var purchasesCompletedByToUse = purchasesAreCompletedBy === PURCHASES_ARE_COMPLETED_BY_TYPE.REVENUECAT ? PURCHASES_ARE_COMPLETED_BY_TYPE.REVENUECAT : undefined; + var storeKitVersionToUse = storeKitVersion; + if (purchasesAreCompletedBy && Purchases.isPurchasesAreCompletedByMyApp(purchasesAreCompletedBy)) { + purchasesCompletedByToUse = PURCHASES_ARE_COMPLETED_BY_TYPE.MY_APP; + storeKitVersionToUse = purchasesAreCompletedBy.storeKitVersion; + if (storeKitVersionToUse === STOREKIT_VERSION.DEFAULT) { + // tslint:disable-next-line:no-console + console.warn("Warning: You should provide the specific StoreKit version you're using in your implementation, and not rely on the DEFAULT."); + } + if (storeKitVersion && storeKitVersionToUse !== storeKitVersion) { + // Typically, console messages aren't used in TS libraries, but in this case it's worth calling out the difference in + // StoreKit versions, and since the difference isn't possible farther down the call chain, we should go ahead + // and log it here. + // tslint:disable-next-line:no-console + console.warn("Warning: The storeKitVersion in purchasesAreCompletedBy does not match the function's storeKitVersion parameter. We will use the value found in purchasesAreCompletedBy."); + } + } + window.cordova.exec(null, null, PLUGIN_NAME, "configure", [apiKey, appUserID, purchasesCompletedByToUse, userDefaultsSuiteName, storeKitVersionToUse, + useAmazon, shouldShowInAppMessagesAutomatically]); + window.cordova.exec(function (customerInfo) { + window.cordova.fireWindowEvent("onCustomerInfoUpdated", customerInfo); + }, null, PLUGIN_NAME, "setupDelegateCallback", []); + this.setupShouldPurchasePromoProductCallback(); + }; + /** + * Gets the Offerings configured in the RevenueCat dashboard + * @param {function(PurchasesOfferings):void} callback Callback triggered after a successful getOfferings call. + * @param {function(PurchasesError):void} errorCallback Callback triggered after an error or when retrieving offerings. + */ + Purchases.getOfferings = function (callback, errorCallback) { + window.cordova.exec(callback, errorCallback, PLUGIN_NAME, "getOfferings", []); + }; + /** + * Fetch the product info + * @param {[string]} productIdentifiers Array of product identifiers + * @param {function(PurchasesStoreProduct[]):void} callback Callback triggered after a successful getProducts call. It will receive an array of product objects. + * @param {function(PurchasesError):void} errorCallback Callback triggered after an error or when retrieving products + * @param {PURCHASE_TYPE} type Optional type of products to fetch, can be inapp or subs. Subs by default + */ + Purchases.getProducts = function (productIdentifiers, callback, errorCallback, type) { + if (type === void 0) { type = PURCHASE_TYPE.SUBS; } + window.cordova.exec(callback, errorCallback, PLUGIN_NAME, "getProducts", [productIdentifiers, type]); + }; + /** + * iOS 18.0+ only. Use this function to retrieve the eligible win-back offers that a subscriber + * is eligible for for a given product. + * + * @param {PurchasesStoreProduct} product The product the user intends to purchase. + * @param {function(PurchasesWinBackOffer[]):void} callback Callback triggered after a successful getEligibleWinBackOffersForProduct call. + * It will receive an array of eligible win-back objects for the provided product. + * @param {function(PurchasesError):void} errorCallback Callback triggered after an error when retrieving eligible win-back offers. + */ + Purchases.getEligibleWinBackOffersForProduct = function (product, callback, errorCallback) { + return __awaiter(this, void 0, void 0, function () { + return __generator(this, function (_a) { + window.cordova.exec(callback, errorCallback, PLUGIN_NAME, "getEligibleWinBackOffersForProduct", [product.identifier]); + return [2 /*return*/]; + }); + }); + }; + /** + * iOS 18.0+ only. Use this function to purchase a product with a win-back offer. + * Fetch eligible win-back offers with getEligibleWinBackOffersForProduct. + * + * @param {PurchasesStoreProduct} product The product the user intends to purchase. + * @param {PurchasesWinBackOffer} winBackOffer The win-back offer the user intends to purchase. + * @param {function(string, CustomerInfo):void} callback Callback triggered after a successful purchaseProductWithWinBackOffer call. + * @param {function(PurchasesError, boolean):void} errorCallback Callback triggered after an error. + */ + Purchases.purchaseProductWithWinBackOffer = function (product, winBackOffer, callback, errorCallback) { + return __awaiter(this, void 0, void 0, function () { + return __generator(this, function (_a) { + window.cordova.exec(callback, function (response) { + var userCancelled = response.userCancelled, error = __rest(response, ["userCancelled"]); + errorCallback({ + error: error, + userCancelled: userCancelled, + }); + }, PLUGIN_NAME, "purchaseProductWithWinBackOffer", [product.identifier, winBackOffer.identifier]); + return [2 /*return*/]; + }); + }); + }; + /** + * iOS 18.0+ only. Use this function to retrieve the eligible win-back offers that a subscriber + * is eligible for for a given package. + * + * @param {PurchasesPackage} package The package the user intends to purchase. + * @param {function(PurchasesWinBackOffer[]):void} callback Callback triggered after a successful getEligibleWinBackOffersForPackage call. + * It will receive an array of eligible win-back objects for the provided package. + * @param {function(PurchasesError):void} errorCallback Callback triggered after an error when retrieving eligible win-back offers. + */ + Purchases.getEligibleWinBackOffersForPackage = function (aPackage, callback, errorCallback) { + return __awaiter(this, void 0, void 0, function () { + return __generator(this, function (_a) { + window.cordova.exec(callback, errorCallback, PLUGIN_NAME, "getEligibleWinBackOffersForProduct", [aPackage.product.identifier]); + return [2 /*return*/]; + }); + }); + }; + /** + * iOS 18.0+ only. Use this function to purchase a product with a win-back offer. + * Fetch eligible win-back offers with getEligibleWinBackOffersForProduct. + * + * @param {PurchasesPackage} aPackage The package the user intends to purchase. + * @param {PurchasesWinBackOffer} winBackOffer The win-back offer the user intends to purchase. + * @param {function(string, CustomerInfo):void} callback Callback triggered after a successful purchasePackageWithWinBackOffer call. + * @param {function(PurchasesError, boolean):void} errorCallback Callback triggered after an error. + */ + Purchases.purchasePackageWithWinBackOffer = function (aPackage, winBackOffer, callback, errorCallback) { + return __awaiter(this, void 0, void 0, function () { + return __generator(this, function (_a) { + window.cordova.exec(callback, function (response) { + var userCancelled = response.userCancelled, error = __rest(response, ["userCancelled"]); + errorCallback({ + error: error, + userCancelled: userCancelled, + }); + }, PLUGIN_NAME, "purchasePackageWithWinBackOffer", [aPackage.identifier, aPackage.offeringIdentifier, winBackOffer.identifier]); + return [2 /*return*/]; + }); + }); + }; + /** + * Make a purchase + * + * @param {string} productIdentifier The product identifier of the product you want to purchase. + * @param {function(string, CustomerInfo):void} callback Callback triggered after a successful purchase. + * @param {function(PurchasesError, boolean):void} errorCallback Callback triggered after an error or when the user cancels the purchase. + * If user cancelled, userCancelled will be true + * @param {UpgradeInfo} upgradeInfo Android only. Optional UpgradeInfo you wish to upgrade from containing the oldSKU + * and the optional prorationMode. + * @param {PURCHASE_TYPE} type Optional type of product, can be inapp or subs. Subs by default + */ + Purchases.purchaseProduct = function (productIdentifier, callback, errorCallback, upgradeInfo, type) { + if (type === void 0) { type = PURCHASE_TYPE.SUBS; } + window.cordova.exec(callback, function (response) { + var userCancelled = response.userCancelled, error = __rest(response, ["userCancelled"]); + errorCallback({ + error: error, + userCancelled: userCancelled, + }); + }, PLUGIN_NAME, "purchaseProduct", [ + productIdentifier, + upgradeInfo !== undefined && upgradeInfo !== null ? upgradeInfo.oldSKU : null, + upgradeInfo !== undefined && upgradeInfo !== null + ? upgradeInfo.prorationMode + : null, + type, + false, + null, + ]); + }; + /** + * Make a purchase + * + * @param {PurchasesStoreProduct} product The product you want to purchase + * @param {function(string, CustomerInfo):void} callback Callback triggered after a successful purchase. + * @param {function(PurchasesError, boolean):void} errorCallback Callback triggered after an error or when the user cancels the purchase + * If user cancelled, userCancelled will be true + * @param {GoogleProductChangeInfo} googleProductChangeInfo Android only. Optional GoogleProductChangeInfo you + * wish to upgrade from containing the oldProductIdentifier and the optional prorationMode. + * @param {boolean} googleIsPersonalizedPrice Android and Google only. Optional boolean indicates personalized pricing on products available for purchase in the EU. + * For compliance with EU regulations. User will see "This price has been customized for you" in the purchase dialog when true. + * See https://developer.android.com/google/play/billing/integrate#personalized-price for more info. + */ + Purchases.purchaseStoreProduct = function (product, callback, errorCallback, googleProductChangeInfo, googleIsPersonalizedPrice) { + if (googleIsPersonalizedPrice === void 0) { googleIsPersonalizedPrice = false; } + window.cordova.exec(callback, function (response) { + var userCancelled = response.userCancelled, error = __rest(response, ["userCancelled"]); + errorCallback({ + error: error, + userCancelled: userCancelled, + }); + }, PLUGIN_NAME, "purchaseProduct", [ + product.identifier, + googleProductChangeInfo !== undefined && googleProductChangeInfo !== null ? googleProductChangeInfo.oldProductIdentifier : null, + googleProductChangeInfo !== undefined && googleProductChangeInfo !== null + ? googleProductChangeInfo.prorationMode + : null, + product.productCategory, + googleIsPersonalizedPrice, + product.presentedOfferingIdentifier, + ]); + }; + /** + * Make a purchase + * + * @param {PurchasesPackage} aPackage The Package you wish to purchase. You can get the Packages by calling getOfferings + * @param {function(string, CustomerInfo):void} callback Callback triggered after a successful purchase. + * @param {function(PurchasesError, boolean):void} errorCallback Callback triggered after an error or when the user cancels the purchase. + * If user cancelled, userCancelled will be true + * @param {UpgradeInfo} upgradeInfo Android only. Optional UpgradeInfo you wish to upgrade from containing the oldSKU + * and the optional prorationMode. + * @param {GoogleProductChangeInfo} googleProductChangeInfo Android only. Optional GoogleProductChangeInfo you + * @param {boolean} googleIsPersonalizedPrice Android and Google only. Optional boolean indicates personalized pricing on products available for purchase in the EU. + * For compliance with EU regulations. User will see "This price has been customized for you" in the purchase dialog when true. + * See https://developer.android.com/google/play/billing/integrate#personalized-price for more info. + */ + Purchases.purchasePackage = function (aPackage, callback, errorCallback, upgradeInfo, googleProductChangeInfo, googleIsPersonalizedPrice) { + if (googleIsPersonalizedPrice === void 0) { googleIsPersonalizedPrice = false; } + var oldProductIdentifier = null; + var prorationMode = null; + if (googleProductChangeInfo !== undefined && googleProductChangeInfo !== null) { + oldProductIdentifier = googleProductChangeInfo.oldProductIdentifier; + prorationMode = googleProductChangeInfo.prorationMode; + } + else if (upgradeInfo !== undefined && upgradeInfo !== null) { + oldProductIdentifier = upgradeInfo.oldSKU; + prorationMode = upgradeInfo.prorationMode; + } + window.cordova.exec(callback, function (response) { + var userCancelled = response.userCancelled, error = __rest(response, ["userCancelled"]); + errorCallback({ + error: error, + userCancelled: userCancelled, + }); + }, PLUGIN_NAME, "purchasePackage", [ + aPackage.identifier, + aPackage.offeringIdentifier, + oldProductIdentifier, + prorationMode, + googleIsPersonalizedPrice, + ]); + }; + /** + * Google only. Make a purchase of a subscriptionOption + * + * @param {SubscriptionOption} subscriptionOption The SubscriptionOption you wish to purchase. You can get the SubscriptionOption from StoreProducts by calling getOfferings + * @param {function(string, CustomerInfo):void} callback Callback triggered after a successful purchase. + * @param {function(PurchasesError, boolean):void} errorCallback Callback triggered after an error or when the user cancels the purchase. + * If user cancelled, userCancelled will be true + * @param {GoogleProductChangeInfo} googleProductChangeInfo Android only. Optional GoogleProductChangeInfo you + * wish to upgrade from containing the oldProductIdentifier and the optional prorationMode. + * @param {boolean} googleIsPersonalizedPrice Android and Google only. Optional boolean indicates personalized pricing on products available for purchase in the EU. + * For compliance with EU regulations. User will see "This price has been customized for you" in the purchase dialog when true. + * See https://developer.android.com/google/play/billing/integrate#personalized-price for more info. + */ + Purchases.purchaseSubscriptionOption = function (subscriptionOption, callback, errorCallback, googleProductChangeInfo, googleIsPersonalizedPrice) { + if (googleIsPersonalizedPrice === void 0) { googleIsPersonalizedPrice = false; } + window.cordova.exec(callback, function (response) { + var userCancelled = response.userCancelled, error = __rest(response, ["userCancelled"]); + errorCallback({ + error: error, + userCancelled: userCancelled, + }); + }, PLUGIN_NAME, "purchaseSubscriptionOption", [ + subscriptionOption.productId, + subscriptionOption.id, + googleProductChangeInfo !== undefined && googleProductChangeInfo !== null ? googleProductChangeInfo.oldProductIdentifier : null, + googleProductChangeInfo !== undefined && googleProductChangeInfo !== null + ? googleProductChangeInfo.prorationMode + : null, + googleIsPersonalizedPrice, + subscriptionOption.presentedOfferingIdentifier, + ]); + }; + /** + * Restores a user's previous purchases and links their appUserIDs to any user's also using those purchases. + * @param {function(CustomerInfo):void} callback Callback that will receive the new customer info after restoring transactions. + * @param {function(PurchasesError):void} errorCallback Callback that will be triggered whenever there is any problem restoring the user transactions. This gets normally triggered if there + * is an error retrieving the new customer info for the new user or the user cancelled the restore + */ + Purchases.restorePurchases = function (callback, errorCallback) { + window.cordova.exec(callback, errorCallback, PLUGIN_NAME, "restorePurchases", []); + }; + /** + * Get the appUserID that is currently in placed in the SDK + * @param {function(string):void} callback Callback that will receive the current appUserID + */ + Purchases.getAppUserID = function (callback) { + window.cordova.exec(callback, null, PLUGIN_NAME, "getAppUserID", []); + }; + /** + * This function will logIn the current user with an appUserID. Typically this would be used after a log in + * to identify a user without calling configure. + * @param {String} appUserID The appUserID that should be linked to the currently user + * @param {function(LogInResult):void} callback Callback that will receive an object that contains the customerInfo after logging in, as well as a boolean indicating + * whether the user has just been created for the first time in the RevenueCat backend. + * @param {function(PurchasesError):void} errorCallback Callback that will be triggered whenever there is any problem logging in. + */ + Purchases.logIn = function (appUserID, callback, errorCallback) { + // noinspection SuspiciousTypeOfGuard + if (typeof appUserID !== "string") { + throw new Error("appUserID needs to be a string"); + } + window.cordova.exec(callback, errorCallback, PLUGIN_NAME, "logIn", [ + appUserID, + ]); + }; + /** + * Logs out the Purchases client clearing the saved appUserID. This will generate a random user id and save it in the cache. + * If the current user is already anonymous, this will produce a PurchasesError. + * @param {function(CustomerInfo):void} callback Callback that will receive the new customer info after resetting + * @param {function(PurchasesError):void} errorCallback Callback that will be triggered whenever there is an error when logging out. + * This could happen for example if logOut is called but the current user is anonymous. + */ + Purchases.logOut = function (callback, errorCallback) { + window.cordova.exec(callback, errorCallback, PLUGIN_NAME, "logOut", []); + }; + /** + * Gets the current customer info. This call will return the cached customer info unless the cache is stale, in which case, + * it will make a network call to retrieve it from the servers. + * @param {function(CustomerInfo):void} callback Callback that will receive the customer info + * @param {function(PurchasesError, boolean):void} errorCallback Callback that will be triggered whenever there is any problem retrieving the customer info + */ + Purchases.getCustomerInfo = function (callback, errorCallback) { + window.cordova.exec(callback, errorCallback, PLUGIN_NAME, "getCustomerInfo", []); + }; + /** + * Enables/Disables debugs logs + * @param {boolean} enabled Enable or disable debug logs + * @deprecated Use {@link setLogLevel} instead. + */ + Purchases.setDebugLogsEnabled = function (enabled) { + window.cordova.exec(null, null, PLUGIN_NAME, "setDebugLogsEnabled", [ + enabled, + ]); + }; + /** + * Used to set the log level. Useful for debugging issues with the lovely team @RevenueCat. + * @param {LOG_LEVEL} level the minimum log level to enable. + */ + Purchases.setLogLevel = function (level) { + window.cordova.exec(null, null, PLUGIN_NAME, "setLogLevel", [ + level, + ]); + }; + /** + * Set a custom log handler for redirecting logs to your own logging system. + * By default, this sends info, warning, and error messages. + * If you wish to receive Debug level messages, see [setLogLevel]. + * @param {LogHandler} logHandler It will get called for each log event. + * Use this function to redirect the log to your own logging system + */ + Purchases.setLogHandler = function (logHandler) { + window.cordova.exec(function (_a) { + var logLevel = _a.logLevel, message = _a.message; + var logLevelEnum = LOG_LEVEL[logLevel]; + logHandler(logLevelEnum, message); + }, null, PLUGIN_NAME, "setLogHandler", []); + }; + /** + * iOS only. + * @param {boolean} enabled Set this property to true *only* when testing the ask-to-buy / SCA purchases flow. + * More information: http://errors.rev.cat/ask-to-buy + */ + Purchases.setSimulatesAskToBuyInSandbox = function (enabled) { + window.cordova.exec(null, null, PLUGIN_NAME, "setSimulatesAskToBuyInSandbox", [ + enabled, + ]); + }; + /** + * This method will send all the purchases to the RevenueCat backend. Call this when using your own implementation + * for subscriptions anytime a sync is needed, like after a successful purchase. + * + * @warning This function should only be called if you're not calling makePurchase. + */ + Purchases.syncPurchases = function () { + window.cordova.exec(null, null, PLUGIN_NAME, "syncPurchases", []); + }; + /** + * @deprecated Use {@link syncAmazonPurchase} instead. + * This method will send a purchase to the RevenueCat backend. This function should only be called if you are + * in Amazon observer mode or performing a client side migration of your current users to RevenueCat. + * + * The receipt IDs are cached if successfully posted so they are not posted more than once. + * + * @param {string} productID Product ID associated to the purchase. + * @param {string} receiptID ReceiptId that represents the Amazon purchase. + * @param {string} amazonUserID Amazon's userID. This parameter will be ignored when syncing a Google purchase. + * @param {(string|null|undefined)} isoCurrencyCode Product's currency code in ISO 4217 format. + * @param {(number|null|undefined)} price Product's price. + */ + Purchases.syncObserverModeAmazonPurchase = function (productID, receiptID, amazonUserID, isoCurrencyCode, price) { + this.syncAmazonPurchase(productID, receiptID, amazonUserID, isoCurrencyCode, price); + }; + /** + * This method will send a purchase to the RevenueCat backend. This function should only be called if you are + * in Amazon observer mode or performing a client side migration of your current users to RevenueCat. + * + * The receipt IDs are cached if successfully posted so they are not posted more than once. + * + * @param {string} productID Product ID associated to the purchase. + * @param {string} receiptID ReceiptId that represents the Amazon purchase. + * @param {string} amazonUserID Amazon's userID. This parameter will be ignored when syncing a Google purchase. + * @param {(string|null|undefined)} isoCurrencyCode Product's currency code in ISO 4217 format. + * @param {(number|null|undefined)} price Product's price. + */ + Purchases.syncAmazonPurchase = function (productID, receiptID, amazonUserID, isoCurrencyCode, price) { + window.cordova.exec(null, null, PLUGIN_NAME, "syncAmazonPurchase", [productID, receiptID, amazonUserID, isoCurrencyCode, price]); + }; + /** + * iOS only. Always returns an error on iOS < 15. + * + * Use this method only if you already have your own IAP implementation using StoreKit 2 and want to use + * RevenueCat's backend. If you are using StoreKit 1 for your implementation, you do not need this method. + * + * You only need to use this method with *new* purchases. Subscription updates are observed automatically. + * + * Important: This should only be used if you have set PurchasesAreCompletedBy to MY_APP during SDK configuration. + * + * @warning You need to finish the transaction yourself after calling this method. + * + * @param {string} productID Product ID that was just purchased + * @returns {Promise} If there was a transacton found and handled for the provided product ID. + */ + Purchases.recordPurchase = function (productID, callback, errorCallback) { + window.cordova.exec(callback, errorCallback, PLUGIN_NAME, "recordPurchase", [productID]); + }; + /** + * Enable automatic collection of Apple Search Ads attribution using AdServices. Disabled by default. + */ + Purchases.enableAdServicesAttributionTokenCollection = function () { + window.cordova.exec(null, null, PLUGIN_NAME, "enableAdServicesAttributionTokenCollection", []); + }; + /** + * @param {function(boolean):void} callback Will be sent a boolean indicating if the `appUserID` has been generated + * by RevenueCat or not. + */ + Purchases.isAnonymous = function (callback) { + window.cordova.exec(callback, null, PLUGIN_NAME, "isAnonymous", []); + }; + /** + * iOS only. Computes whether or not a user is eligible for the introductory pricing period of a given product. + * You should use this method to determine whether or not you show the user the normal product price or the + * introductory price. This also applies to trials (trials are considered a type of introductory pricing). + * + * @note Subscription groups are automatically collected for determining eligibility. If RevenueCat can't + * definitively compute the eligibility, most likely because of missing group information, it will return + * `INTRO_ELIGIBILITY_STATUS_UNKNOWN`. The best course of action on unknown status is to display the non-intro + * pricing, to not create a misleading situation. To avoid this, make sure you are testing with the latest version of + * iOS so that the subscription group can be collected by the SDK. Android always returns INTRO_ELIGIBILITY_STATUS_UNKNOWN. + * + * @param productIdentifiers Array of product identifiers for which you want to compute eligibility + * @param callback Will be sent a map of IntroEligibility per productId + */ + Purchases.checkTrialOrIntroductoryPriceEligibility = function (productIdentifiers, callback) { + window.cordova.exec(callback, null, PLUGIN_NAME, "checkTrialOrIntroductoryPriceEligibility", [productIdentifiers]); + }; + /** + * Sets a function to be called on purchases initiated on the Apple App Store. This is only used in iOS. + * @param {ShouldPurchasePromoProductListener} shouldPurchasePromoProductListener Called when a user initiates a + * promotional in-app purchase from the App Store. If your app is able to handle a purchase at the current time, run + * the deferredPurchase function. If the app is not in a state to make a purchase: cache the deferredPurchase, then + * call the deferredPurchase when the app is ready to make the promotional purchase. + * If the purchase should never be made, you don't need to ever call the deferredPurchase and the app will not + * proceed with promotional purchases. + */ + Purchases.addShouldPurchasePromoProductListener = function (shouldPurchasePromoProductListener) { + if (typeof shouldPurchasePromoProductListener !== "function") { + throw new Error("addShouldPurchasePromoProductListener needs a function"); + } + shouldPurchasePromoProductListeners.push(shouldPurchasePromoProductListener); + }; + /** + * Removes a given ShouldPurchasePromoProductListener + * @param {ShouldPurchasePromoProductListener} listenerToRemove ShouldPurchasePromoProductListener reference of the listener to remove + * @returns {boolean} True if listener was removed, false otherwise + */ + Purchases.removeShouldPurchasePromoProductListener = function (listenerToRemove) { + if (shouldPurchasePromoProductListeners.indexOf(listenerToRemove) !== -1) { + shouldPurchasePromoProductListeners = shouldPurchasePromoProductListeners.filter(function (listener) { return listenerToRemove !== listener; }); + return true; + } + return false; + }; + /** + * Invalidates the cache for customer information. + * + * Most apps will not need to use this method; invalidating the cache can leave your app in an invalid state. + * Refer to https://docs.revenuecat.com/docs/customer-info#section-get-user-information for more information on + * using the cache properly. + * + * This is useful for cases where customer information might have been updated outside of the + * app, like if a promotional subscription is granted through the RevenueCat dashboard. + */ + Purchases.invalidateCustomerInfoCache = function () { + window.cordova.exec(null, null, PLUGIN_NAME, "invalidateCustomerInfoCache", []); + }; + /** + * iOS only. Presents a code redemption sheet, useful for redeeming offer codes + * Refer to https://docs.revenuecat.com/docs/ios-subscription-offers#offer-codes for more information on how + * to configure and use offer codes. + */ + Purchases.presentCodeRedemptionSheet = function () { + window.cordova.exec(null, null, PLUGIN_NAME, "presentCodeRedemptionSheet", []); + }; + /** + * Subscriber attributes are useful for storing additional, structured information on a user. + * Since attributes are writable using a public key they should not be used for + * managing secure or sensitive information such as subscription status, coins, etc. + * + * Key names starting with "$" are reserved names used by RevenueCat. For a full list of key + * restrictions refer to our guide: https://docs.revenuecat.com/docs/subscriber-attributes + * + * @param attributes Map of attributes by key. Set the value as an empty string to delete an attribute. + */ + Purchases.setAttributes = function (attributes) { + window.cordova.exec(null, null, PLUGIN_NAME, "setAttributes", [attributes]); + }; + /** + * Subscriber attribute associated with the email address for the user + * + * @param email Empty String or null will delete the subscriber attribute. + */ + Purchases.setEmail = function (email) { + window.cordova.exec(null, null, PLUGIN_NAME, "setEmail", [email]); + }; + /** + * Subscriber attribute associated with the phone number for the user + * + * @param phoneNumber Empty String or null will delete the subscriber attribute. + */ + Purchases.setPhoneNumber = function (phoneNumber) { + window.cordova.exec(null, null, PLUGIN_NAME, "setPhoneNumber", [phoneNumber]); + }; + /** + * Subscriber attribute associated with the display name for the user + * + * @param displayName Empty String or null will delete the subscriber attribute. + */ + Purchases.setDisplayName = function (displayName) { + window.cordova.exec(null, null, PLUGIN_NAME, "setDisplayName", [displayName]); + }; + /** + * Subscriber attribute associated with the push token for the user + * + * @param pushToken Empty String or null will delete the subscriber attribute. + */ + Purchases.setPushToken = function (pushToken) { + window.cordova.exec(null, null, PLUGIN_NAME, "setPushToken", [pushToken]); + }; + /** + * Subscriber attribute associated with the Adjust Id for the user + * Required for the RevenueCat Adjust integration + * + * @param adjustID Empty String or null will delete the subscriber attribute. + */ + Purchases.setAdjustID = function (adjustID) { + window.cordova.exec(null, null, PLUGIN_NAME, "setAdjustID", [adjustID]); + }; + /** + * Subscriber attribute associated with the AppsFlyer Id for the user + * Required for the RevenueCat AppsFlyer integration + * @param appsflyerID Empty String or null will delete the subscriber attribute. + */ + Purchases.setAppsflyerID = function (appsflyerID) { + window.cordova.exec(null, null, PLUGIN_NAME, "setAppsflyerID", [appsflyerID]); + }; + /** + * Subscriber attribute associated with the Facebook SDK Anonymous Id for the user + * Recommended for the RevenueCat Facebook integration + * + * @param fbAnonymousID Empty String or null will delete the subscriber attribute. + */ + Purchases.setFBAnonymousID = function (fbAnonymousID) { + window.cordova.exec(null, null, PLUGIN_NAME, "setFBAnonymousID", [fbAnonymousID]); + }; + /** + * Subscriber attribute associated with the mParticle Id for the user + * Recommended for the RevenueCat mParticle integration + * + * @param mparticleID Empty String or null will delete the subscriber attribute. + */ + Purchases.setMparticleID = function (mparticleID) { + window.cordova.exec(null, null, PLUGIN_NAME, "setMparticleID", [mparticleID]); + }; + /** + * Subscriber attribute associated with the OneSignal Player Id for the user + * Required for the RevenueCat OneSignal integration + * + * @param onesignalID Empty String or null will delete the subscriber attribute. + */ + Purchases.setOnesignalID = function (onesignalID) { + window.cordova.exec(null, null, PLUGIN_NAME, "setOnesignalID", [onesignalID]); + }; + /** + * Subscriber attribute associated with the Airship Channel Id for the user + * Required for the RevenueCat Airship integration + * + * @param airshipChannelID Empty String or null will delete the subscriber attribute. + */ + Purchases.setAirshipChannelID = function (airshipChannelID) { + window.cordova.exec(null, null, PLUGIN_NAME, "setAirshipChannelID", [airshipChannelID]); + }; + /** + * Subscriber attribute associated with the Firebase App Instance ID for the user + * Required for the RevenueCat Firebase integration + * + * @param firebaseAppInstanceID Empty String or null will delete the subscriber attribute. + */ + Purchases.setFirebaseAppInstanceID = function (firebaseAppInstanceID) { + window.cordova.exec(null, null, PLUGIN_NAME, "setFirebaseAppInstanceID", [firebaseAppInstanceID]); + }; + /** + * Subscriber attribute associated with the Mixpanel Distinct ID for the user + * Required for the RevenueCat Mixpanel integration + * + * @param mixpanelDistinctID Empty String or null will delete the subscriber attribute. + */ + Purchases.setMixpanelDistinctID = function (mixpanelDistinctID) { + window.cordova.exec(null, null, PLUGIN_NAME, "setMixpanelDistinctID", [mixpanelDistinctID]); + }; + /** + * Subscriber attribute associated with the CleverTap ID for the user + * Required for the RevenueCat CleverTap integration + * + * @param cleverTapID Empty String or null will delete the subscriber attribute. + */ + Purchases.setCleverTapID = function (cleverTapID) { + window.cordova.exec(null, null, PLUGIN_NAME, "setCleverTapID", [cleverTapID]); + }; + /** + * Subscriber attribute associated with the install media source for the user + * + * @param mediaSource Empty String or null will delete the subscriber attribute. + */ + Purchases.setMediaSource = function (mediaSource) { + window.cordova.exec(null, null, PLUGIN_NAME, "setMediaSource", [mediaSource]); + }; + /** + * Subscriber attribute associated with the install campaign for the user + * + * @param campaign Empty String or null will delete the subscriber attribute. + */ + Purchases.setCampaign = function (campaign) { + window.cordova.exec(null, null, PLUGIN_NAME, "setCampaign", [campaign]); + }; + /** + * Subscriber attribute associated with the install ad group for the user + * + * @param adGroup Empty String or null will delete the subscriber attribute. + */ + Purchases.setAdGroup = function (adGroup) { + window.cordova.exec(null, null, PLUGIN_NAME, "setAdGroup", [adGroup]); + }; + /** + * Subscriber attribute associated with the install ad for the user + * + * @param ad Empty String or null will delete the subscriber attribute. + */ + Purchases.setAd = function (ad) { + window.cordova.exec(null, null, PLUGIN_NAME, "setAd", [ad]); + }; + /** + * Subscriber attribute associated with the install keyword for the user + * + * @param keyword Empty String or null will delete the subscriber attribute. + */ + Purchases.setKeyword = function (keyword) { + window.cordova.exec(null, null, PLUGIN_NAME, "setKeyword", [keyword]); + }; + /** + * Subscriber attribute associated with the install ad creative for the user + * + * @param creative Empty String or null will delete the subscriber attribute. + */ + Purchases.setCreative = function (creative) { + window.cordova.exec(null, null, PLUGIN_NAME, "setCreative", [creative]); + }; + /** + * Automatically collect subscriber attributes associated with the device identifiers. + * $idfa, $idfv, $ip on iOS + * $gpsAdId, $androidId, $ip on Android + */ + Purchases.collectDeviceIdentifiers = function () { + window.cordova.exec(null, null, PLUGIN_NAME, "collectDeviceIdentifiers", []); + }; + /** + * Set this property to your proxy URL before configuring Purchases *only* if you've received a proxy key value from your RevenueCat contact. + * @param url Proxy URL as a string. + */ + Purchases.setProxyURL = function (url) { + window.cordova.exec(null, null, PLUGIN_NAME, "setProxyURLString", [url]); + }; + /** + * Check if billing is supported for the current user (meaning IN-APP purchases are supported) + * and optionally, whether a list of specified feature types are supported. + * + * Note: Billing features are only relevant to Google Play Android users. + * For other stores and platforms, billing features won't be checked. + * @param {[BILLING_FEATURE]} features An array of feature types to check for support. Feature types must be one of + * [BILLING_FEATURE]. By default, is an empty list and no specific feature support will be checked. + * @param {function(boolean):void} callback Will be sent true if billing is supported, false otherwise. + * @param {function(PurchasesError):void} errorCallback Callback triggered after an error or when checking if billing + * is supported. + */ + Purchases.canMakePayments = function (features, callback, errorCallback) { + if (features === void 0) { features = []; } + window.cordova.exec(callback, errorCallback, PLUGIN_NAME, "canMakePayments", [features]); + }; + /** + * iOS 15+ only. Presents a refund request sheet in the current window scene for + * the latest transaction associated with the active entitlement. + * + * If the request was unsuccessful, no active entitlements could be found for + * the user, or multiple active entitlements were found for the user, + * the promise will return an error. + * If called in an unsupported platform (iOS < 15), an `unsupportedError` will be sent to the callback. + * + * Important: This method should only be used if your user can only have a single active entitlement at a given time. + * If a user could have more than one entitlement at a time, use `beginRefundRequestForEntitlement` instead. + * + * @param {function(REFUND_REQUEST_STATUS):void} callback REFUND_REQUEST_STATUS: The status of the refund request. + * Keep in mind the status could be REFUND_REQUEST_STATUS.USER_CANCELLED + * @param {function(PurchasesError):void} errorCallback Callback triggered after an error when beginning refund + * request for active entitlement. + */ + Purchases.beginRefundRequestForActiveEntitlement = function (callback, errorCallback) { + window.cordova.exec(function (refundRequestStatusInt) { + var refundRequestStatus = Purchases.convertIntToRefundRequestStatus(refundRequestStatusInt); + callback(refundRequestStatus); + }, errorCallback, PLUGIN_NAME, "beginRefundRequestForActiveEntitlement", []); + }; + /** + * iOS 15+ only. Presents a refund request sheet in the current window scene for + * the latest transaction associated with the `entitlement`. + * + * If the request was unsuccessful, the promise will return an error. + * If called in an unsupported platform (iOS < 15), an `unsupportedError` will be sent to the callback. + * + * @param entitlementInfo The entitlement to begin a refund request for. + * @param {function(REFUND_REQUEST_STATUS):void} callback REFUND_REQUEST_STATUS: The status of the refund request. + * Keep in mind the status could be REFUND_REQUEST_STATUS.USER_CANCELLED + * @param {function(PurchasesError):void} errorCallback Callback triggered after an error when beginning refund + * request for an entitlement. + */ + Purchases.beginRefundRequestForEntitlement = function (entitlementInfo, callback, errorCallback) { + window.cordova.exec(function (refundRequestStatusInt) { + var refundRequestStatus = Purchases.convertIntToRefundRequestStatus(refundRequestStatusInt); + callback(refundRequestStatus); + }, errorCallback, PLUGIN_NAME, "beginRefundRequestForEntitlementId", [entitlementInfo.identifier]); + }; + /** + * iOS 15+ only. Presents a refund request sheet in the current window scene for + * the latest transaction associated with the `product`. + * + * If the request was unsuccessful, the promise will return an error. + * If called in an unsupported platform (iOS < 15), an `unsupportedError` will be sent to the callback. + * + * @param storeProduct The StoreProduct to begin a refund request for. + * @param {function(REFUND_REQUEST_STATUS):void} callback REFUND_REQUEST_STATUS: The status of the refund request. + * Keep in mind the status could be REFUND_REQUEST_STATUS.USER_CANCELLED + * @param {function(PurchasesError):void} errorCallback Callback triggered after an error when beginning refund + * request for a product. + */ + Purchases.beginRefundRequestForProduct = function (storeProduct, callback, errorCallback) { + window.cordova.exec(function (refundRequestStatusInt) { + var refundRequestStatus = Purchases.convertIntToRefundRequestStatus(refundRequestStatusInt); + callback(refundRequestStatus); + }, errorCallback, PLUGIN_NAME, "beginRefundRequestForProductId", [storeProduct.identifier]); + }; + /** + * Shows in-app messages available from the App Store or Google Play. You need to disable messages from showing + * automatically using [PurchasesConfiguration.shouldShowInAppMessagesAutomatically]. + * + * Note: In iOS, this requires version 16+. In older versions the promise will be resolved successfully + * immediately. + * + * @param messageTypes An array of message types that the stores can display inside your app. Must be one of + * [IN_APP_MESSAGE_TYPE]. By default, is undefined and all message types will be shown. + */ + Purchases.showInAppMessages = function (messageTypes) { + window.cordova.exec(null, null, PLUGIN_NAME, "showInAppMessages", [messageTypes]); + }; + /** + * Fetches the virtual currencies for the current subscriber. + * + * @param {function(PurchasesVirtualCurrencies):void} callback Callback that will receive the subscriber's virtual currencies. + * @param {function(PurchasesError):void} errorCallback Callback that will be triggered whenever there is a problem retrieving the subscriber's virtual currencies. + */ + Purchases.getVirtualCurrencies = function (callback, errorCallback) { + window.cordova.exec(callback, errorCallback, PLUGIN_NAME, "getVirtualCurrencies", []); + }; + /** + * Invalidates the cache for virtual currencies. + * + * This is useful for cases where a virtual currency's balance might have been updated + * outside of the app, like if you decreased a user's balance from the user spending a virtual currency, + * or if you increased the balance from your backend using the server APIs. + */ + Purchases.invalidateVirtualCurrenciesCache = function () { + window.cordova.exec(null, null, PLUGIN_NAME, "invalidateVirtualCurrenciesCache", []); + }; + /** + * The currently cached `PurchasesVirtualCurrencies` if one is available. + * This value will remain null until virtual currencies have been fetched at + * least once with `Purchases.getVirtualCurrencies` or an equivalent function. + * + * @param {function(PurchasesVirtualCurrencies):void} callback Callback that will be triggered with the currently cached virtual currencies for the current subscriber. + */ + Purchases.getCachedVirtualCurrencies = function (callback) { + return window.cordova.exec(callback, null, PLUGIN_NAME, "getCachedVirtualCurrencies", []); + }; + Purchases.setupShouldPurchasePromoProductCallback = function () { + var _this = this; + window.cordova.exec(function (_a) { + var callbackID = _a.callbackID; + shouldPurchasePromoProductListeners.forEach(function (listener) { + return listener(_this.getMakeDeferredPurchaseFunction(callbackID)); + }); + }, null, PLUGIN_NAME, "setupShouldPurchasePromoProductCallback", []); + }; + Purchases.getMakeDeferredPurchaseFunction = function (callbackID) { + return function () { return window.cordova.exec(null, null, PLUGIN_NAME, "makeDeferredPurchase", [callbackID]); }; + }; + Purchases.convertIntToRefundRequestStatus = function (refundRequestStatusInt) { + switch (refundRequestStatusInt) { + case 0: + return REFUND_REQUEST_STATUS.SUCCESS; + case 1: + return REFUND_REQUEST_STATUS.USER_CANCELLED; + default: + return REFUND_REQUEST_STATUS.ERROR; + } + }; + Purchases.isPurchasesAreCompletedByMyApp = function (obj) { + return (typeof obj === "object" && + obj !== null && + obj.type === + PURCHASES_ARE_COMPLETED_BY_TYPE.MY_APP); + }; + /** + * Enum for attribution networks + * @readonly + * @enum {Number} + */ + Purchases.ATTRIBUTION_NETWORK = ATTRIBUTION_NETWORK; + /** + * Supported SKU types. + * @readonly + * @enum {string} + */ + Purchases.PURCHASE_TYPE = PURCHASE_TYPE; + /** + * Supported product categories. + * @readonly + * @enum {string} + */ + Purchases.PRODUCT_CATEGORY = PRODUCT_CATEGORY; + /** + * Enum for billing features. + * Currently, these are only relevant for Google Play Android users: + * https://developer.android.com/reference/com/android/billingclient/api/BillingClient.FeatureType + */ + Purchases.BILLING_FEATURE = BILLING_FEATURE; + /** + * Enum with possible return states for beginning refund request. + * @readonly + * @enum {string} + */ + Purchases.REFUND_REQUEST_STATUS = REFUND_REQUEST_STATUS; + /** + * Replace SKU's ProrationMode. + * @readonly + * @enum {number} + */ + Purchases.PRORATION_MODE = PRORATION_MODE; + /** + * Enumeration of all possible Package types. + * @readonly + * @enum {string} + */ + Purchases.PACKAGE_TYPE = PACKAGE_TYPE; + /** + * Enum of different possible states for intro price eligibility status. + * @readonly + * @enum {number} + */ + Purchases.INTRO_ELIGIBILITY_STATUS = INTRO_ELIGIBILITY_STATUS; + /** + * Enum of different possible log levels. + * @readonly + * @enum {string} + */ + Purchases.LOG_LEVEL = LOG_LEVEL; + /** + * Enum of different possible in-app message types. + * @readonly + * @enum {string} + */ + Purchases.IN_APP_MESSAGE_TYPE = IN_APP_MESSAGE_TYPE; + /** + * Modes for completing the purchase process. + * @readonly + * @enum {string} + */ + Purchases.PURCHASES_ARE_COMPLETED_BY_TYPE = PURCHASES_ARE_COMPLETED_BY_TYPE; + /** + * Defines which version of StoreKit may be used. + * @readonly + * @enum {string} + */ + Purchases.STOREKIT_VERSION = STOREKIT_VERSION; + return Purchases; +}()); +if (!window.plugins) { + window.plugins = {}; +} +if (!window.plugins.Purchases) { + window.plugins.Purchases = new Purchases(); +} +if (typeof module !== "undefined" && module.exports) { + module.exports = Purchases; +} +exports.default = Purchases; From f2e96ba5f808d20d0889299e55194b45c7eda979 Mon Sep 17 00:00:00 2001 From: Antonio Pallares Date: Tue, 31 Mar 2026 02:20:21 +0200 Subject: [PATCH 22/22] Force-track Cordova runtime files for iOS platform www The root .gitignore excludes www/ which also matches platforms/ios/www/. On CI, the Cordova runtime files (cordova.js, cordova_plugins.js, plugin.js) were missing, preventing the Purchases JS object from being defined. Made-with: Cursor --- .../platforms/ios/www/cordova.js | 2175 +++++++++++++++++ .../platforms/ios/www/cordova_plugins.js | 17 + .../cordova-plugin-purchases/www/plugin.js | 1280 ++++++++++ 3 files changed, 3472 insertions(+) create mode 100644 e2e-tests/MaestroTestApp/platforms/ios/www/cordova.js create mode 100644 e2e-tests/MaestroTestApp/platforms/ios/www/cordova_plugins.js create mode 100644 e2e-tests/MaestroTestApp/platforms/ios/www/plugins/cordova-plugin-purchases/www/plugin.js diff --git a/e2e-tests/MaestroTestApp/platforms/ios/www/cordova.js b/e2e-tests/MaestroTestApp/platforms/ios/www/cordova.js new file mode 100644 index 00000000..136d639b --- /dev/null +++ b/e2e-tests/MaestroTestApp/platforms/ios/www/cordova.js @@ -0,0 +1,2175 @@ +// Platform: cordova-ios +// cordova-js 6.1.0 +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +*/ +;(function() { +var PLATFORM_VERSION_BUILD_LABEL = '8.0.0'; +// file: src/scripts/require.js +var require; +var define; + +(function () { + var modules = {}; + // Stack of moduleIds currently being built. + var requireStack = []; + // Map of module ID -> index into requireStack of modules currently being built. + var inProgressModules = {}; + var SEPARATOR = '.'; + + function build (module) { + var factory = module.factory; + var localRequire = function (id) { + var resultantId = id; + // Its a relative path, so lop off the last portion and add the id (minus "./") + if (id.charAt(0) === '.') { + resultantId = module.id.slice(0, module.id.lastIndexOf(SEPARATOR)) + SEPARATOR + id.slice(2); + } + return require(resultantId); + }; + module.exports = {}; + delete module.factory; + factory(localRequire, module.exports, module); + return module.exports; + } + + require = function (id) { + if (!modules[id]) { + throw new Error('module ' + id + ' not found'); + } else if (id in inProgressModules) { + var cycle = requireStack.slice(inProgressModules[id]).join('->') + '->' + id; + throw new Error('Cycle in require graph: ' + cycle); + } + if (modules[id].factory) { + try { + inProgressModules[id] = requireStack.length; + requireStack.push(id); + return build(modules[id]); + } finally { + delete inProgressModules[id]; + requireStack.pop(); + } + } + return modules[id].exports; + }; + + define = function (id, factory) { + if (Object.prototype.hasOwnProperty.call(modules, id)) { + throw new Error('module ' + id + ' already defined'); + } + + modules[id] = { + id: id, + factory: factory + }; + }; + + define.remove = function (id) { + delete modules[id]; + }; + + define.moduleMap = modules; +})(); + +// Export for use in node +if (typeof module === 'object' && typeof require === 'function') { + module.exports.require = require; + module.exports.define = define; +} + +// file: src/cordova.js +define("cordova", function(require, exports, module) { + +// Workaround for Windows 10 in hosted environment case +// http://www.w3.org/html/wg/drafts/html/master/browsers.html#named-access-on-the-window-object +if (window.cordova && !(window.cordova instanceof HTMLElement)) { + throw new Error('cordova already defined'); +} + +var channel = require('cordova/channel'); +var platform = require('cordova/platform'); + +/** + * Intercept calls to addEventListener + removeEventListener and handle deviceready, + * resume, and pause events. + */ +var m_document_addEventListener = document.addEventListener; +var m_document_removeEventListener = document.removeEventListener; +var m_window_addEventListener = window.addEventListener; +var m_window_removeEventListener = window.removeEventListener; + +/** + * Houses custom event handlers to intercept on document + window event listeners. + */ +var documentEventHandlers = {}; +var windowEventHandlers = {}; + +document.addEventListener = function (evt, handler, capture) { + var e = evt.toLowerCase(); + if (typeof documentEventHandlers[e] !== 'undefined') { + documentEventHandlers[e].subscribe(handler); + } else { + m_document_addEventListener.call(document, evt, handler, capture); + } +}; + +window.addEventListener = function (evt, handler, capture) { + var e = evt.toLowerCase(); + if (typeof windowEventHandlers[e] !== 'undefined') { + windowEventHandlers[e].subscribe(handler); + } else { + m_window_addEventListener.call(window, evt, handler, capture); + } +}; + +document.removeEventListener = function (evt, handler, capture) { + var e = evt.toLowerCase(); + // If unsubscribing from an event that is handled by a plugin + if (typeof documentEventHandlers[e] !== 'undefined') { + documentEventHandlers[e].unsubscribe(handler); + } else { + m_document_removeEventListener.call(document, evt, handler, capture); + } +}; + +window.removeEventListener = function (evt, handler, capture) { + var e = evt.toLowerCase(); + // If unsubscribing from an event that is handled by a plugin + if (typeof windowEventHandlers[e] !== 'undefined') { + windowEventHandlers[e].unsubscribe(handler); + } else { + m_window_removeEventListener.call(window, evt, handler, capture); + } +}; + +function createEvent (type, data) { + var event = document.createEvent('Events'); + event.initEvent(type, false, false); + if (data) { + for (var i in data) { + if (Object.prototype.hasOwnProperty.call(data, i)) { + event[i] = data[i]; + } + } + } + return event; +} + +var cordova = { + define: define, + require: require, + version: PLATFORM_VERSION_BUILD_LABEL, + platformVersion: PLATFORM_VERSION_BUILD_LABEL, + platformId: platform.id, + + /** + * Methods to add/remove your own addEventListener hijacking on document + window. + */ + addWindowEventHandler: function (event) { + return (windowEventHandlers[event] = channel.create(event)); + }, + addStickyDocumentEventHandler: function (event) { + return (documentEventHandlers[event] = channel.createSticky(event)); + }, + addDocumentEventHandler: function (event) { + return (documentEventHandlers[event] = channel.create(event)); + }, + removeWindowEventHandler: function (event) { + delete windowEventHandlers[event]; + }, + removeDocumentEventHandler: function (event) { + delete documentEventHandlers[event]; + }, + + /** + * Retrieve original event handlers that were replaced by Cordova + * + * @return object + */ + getOriginalHandlers: function () { + return { + document: { + addEventListener: m_document_addEventListener, + removeEventListener: m_document_removeEventListener + }, + window: { + addEventListener: m_window_addEventListener, + removeEventListener: m_window_removeEventListener + } + }; + }, + + /** + * Method to fire event from native code + * bNoDetach is required for events which cause an exception which needs to be caught in native code + */ + fireDocumentEvent: function (type, data, bNoDetach) { + var evt = createEvent(type, data); + if (typeof documentEventHandlers[type] !== 'undefined') { + if (bNoDetach) { + documentEventHandlers[type].fire(evt); + } else { + setTimeout(function () { + // Fire deviceready on listeners that were registered before cordova.js was loaded. + if (type === 'deviceready') { + document.dispatchEvent(evt); + } + documentEventHandlers[type].fire(evt); + }, 0); + } + } else { + document.dispatchEvent(evt); + } + }, + + fireWindowEvent: function (type, data) { + var evt = createEvent(type, data); + if (typeof windowEventHandlers[type] !== 'undefined') { + setTimeout(function () { + windowEventHandlers[type].fire(evt); + }, 0); + } else { + window.dispatchEvent(evt); + } + }, + + /** + * Plugin callback mechanism. + */ + // Randomize the starting callbackId to avoid collisions after refreshing or navigating. + // This way, it's very unlikely that any new callback would get the same callbackId as an old callback. + callbackId: Math.floor(Math.random() * 2000000000), + callbacks: {}, + callbackStatus: { + NO_RESULT: 0, + OK: 1, + CLASS_NOT_FOUND_EXCEPTION: 2, + ILLEGAL_ACCESS_EXCEPTION: 3, + INSTANTIATION_EXCEPTION: 4, + MALFORMED_URL_EXCEPTION: 5, + IO_EXCEPTION: 6, + INVALID_ACTION: 7, + JSON_EXCEPTION: 8, + ERROR: 9 + }, + + /** + * Called by native code when returning successful result from an action. + */ + callbackSuccess: function (callbackId, args) { + cordova.callbackFromNative(callbackId, true, args.status, [args.message], args.keepCallback); + }, + + /** + * Called by native code when returning error result from an action. + */ + callbackError: function (callbackId, args) { + // TODO: Deprecate callbackSuccess and callbackError in favour of callbackFromNative. + // Derive success from status. + cordova.callbackFromNative(callbackId, false, args.status, [args.message], args.keepCallback); + }, + + /** + * Called by native code when returning the result from an action. + */ + callbackFromNative: function (callbackId, isSuccess, status, args, keepCallback) { + try { + var callback = cordova.callbacks[callbackId]; + if (callback) { + if (isSuccess && status === cordova.callbackStatus.OK) { + callback.success && callback.success.apply(null, args); + } else if (!isSuccess) { + callback.fail && callback.fail.apply(null, args); + } + /* + else + Note, this case is intentionally not caught. + this can happen if isSuccess is true, but callbackStatus is NO_RESULT + which is used to remove a callback from the list without calling the callbacks + typically keepCallback is false in this case + */ + // Clear callback if not expecting any more results + if (!keepCallback) { + delete cordova.callbacks[callbackId]; + } + } + } catch (err) { + var msg = 'Error in ' + (isSuccess ? 'Success' : 'Error') + ' callbackId: ' + callbackId + ' : ' + err; + cordova.fireWindowEvent('cordovacallbackerror', { message: msg, error: err }); + throw err; + } + }, + + addConstructor: function (func) { + channel.onCordovaReady.subscribe(function () { + try { + func(); + } catch (e) { + console.log('Failed to run constructor: ' + e); + } + }); + } +}; + +module.exports = cordova; + +}); + +// file: src/common/argscheck.js +define("cordova/argscheck", function(require, exports, module) { + +var utils = require('cordova/utils'); + +var moduleExports = module.exports; + +var typeMap = { + A: 'Array', + D: 'Date', + N: 'Number', + S: 'String', + F: 'Function', + O: 'Object' +}; + +function extractParamName (callee, argIndex) { + return (/\(\s*([^)]*?)\s*\)/).exec(callee)[1].split(/\s*,\s*/)[argIndex]; +} + +/** + * Checks the given arguments' types and throws if they are not as expected. + * + * `spec` is a string where each character stands for the required type of the + * argument at the same position. In other words: the character at `spec[i]` + * specifies the required type for `args[i]`. The characters in `spec` are the + * first letter of the required type's name. The supported types are: + * + * Array, Date, Number, String, Function, Object + * + * Lowercase characters specify arguments that must not be `null` or `undefined` + * while uppercase characters allow those values to be passed. + * + * Finally, `*` can be used to allow any type at the corresponding position. + * + * @example + * function foo (arr, opts) { + * // require `arr` to be an Array and `opts` an Object, null or undefined + * checkArgs('aO', 'my.package.foo', arguments); + * // ... + * } + * @param {String} spec - the type specification for `args` as described above + * @param {String} functionName - full name of the callee. + * Used in the error message + * @param {Array|arguments} args - the arguments to be checked against `spec` + * @param {Function} [opt_callee=args.callee] - the recipient of `args`. + * Used to extract parameter names for the error message + * @throws {TypeError} if args do not satisfy spec + */ +function checkArgs (spec, functionName, args, opt_callee) { + if (!moduleExports.enableChecks) { + return; + } + var errMsg = null; + var typeName; + for (var i = 0; i < spec.length; ++i) { + var c = spec.charAt(i); + var cUpper = c.toUpperCase(); + var arg = args[i]; + // Asterix means allow anything. + if (c === '*') { + continue; + } + typeName = utils.typeName(arg); + if ((arg === null || arg === undefined) && c === cUpper) { + continue; + } + if (typeName !== typeMap[cUpper]) { + errMsg = 'Expected ' + typeMap[cUpper]; + break; + } + } + if (errMsg) { + errMsg += ', but got ' + typeName + '.'; + errMsg = 'Wrong type for parameter "' + extractParamName(opt_callee || args.callee, i) + '" of ' + functionName + ': ' + errMsg; + // Don't log when running unit tests. + if (typeof jasmine === 'undefined') { + console.error(errMsg); + } + throw TypeError(errMsg); + } +} + +function getValue (value, defaultValue) { + return value === undefined ? defaultValue : value; +} + +moduleExports.checkArgs = checkArgs; +moduleExports.getValue = getValue; +moduleExports.enableChecks = true; + +}); + +// file: src/common/base64.js +define("cordova/base64", function(require, exports, module) { + +var base64 = exports; + +base64.fromArrayBuffer = function (arrayBuffer) { + var array = new Uint8Array(arrayBuffer); + return uint8ToBase64(array); +}; + +base64.toArrayBuffer = function (str) { + var decodedStr = atob(str); + var arrayBuffer = new ArrayBuffer(decodedStr.length); + var array = new Uint8Array(arrayBuffer); + for (var i = 0, len = decodedStr.length; i < len; i++) { + array[i] = decodedStr.charCodeAt(i); + } + return arrayBuffer; +}; + +// ------------------------------------------------------------------------------ + +/* This code is based on the performance tests at http://jsperf.com/b64tests + * This 12-bit-at-a-time algorithm was the best performing version on all + * platforms tested. + */ + +var b64_6bit = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; +var b64_12bit; + +var b64_12bitTable = function () { + b64_12bit = []; + for (var i = 0; i < 64; i++) { + for (var j = 0; j < 64; j++) { + b64_12bit[i * 64 + j] = b64_6bit[i] + b64_6bit[j]; + } + } + b64_12bitTable = function () { return b64_12bit; }; + return b64_12bit; +}; + +function uint8ToBase64 (rawData) { + var numBytes = rawData.byteLength; + var output = ''; + var segment; + var table = b64_12bitTable(); + for (var i = 0; i < numBytes - 2; i += 3) { + segment = (rawData[i] << 16) + (rawData[i + 1] << 8) + rawData[i + 2]; + output += table[segment >> 12]; + output += table[segment & 0xfff]; + } + if (numBytes - i === 2) { + segment = (rawData[i] << 16) + (rawData[i + 1] << 8); + output += table[segment >> 12]; + output += b64_6bit[(segment & 0xfff) >> 6]; + output += '='; + } else if (numBytes - i === 1) { + segment = (rawData[i] << 16); + output += table[segment >> 12]; + output += '=='; + } + return output; +} + +}); + +// file: src/common/builder.js +define("cordova/builder", function(require, exports, module) { + +var utils = require('cordova/utils'); + +function each (objects, func, context) { + for (var prop in objects) { + if (Object.prototype.hasOwnProperty.call(objects, prop)) { + func.apply(context, [objects[prop], prop]); + } + } +} + +function clobber (obj, key, value) { + var needsProperty = false; + try { + obj[key] = value; + } catch (e) { + needsProperty = true; + } + // Getters can only be overridden by getters. + if (needsProperty || obj[key] !== value) { + utils.defineGetter(obj, key, function () { + return value; + }); + } +} + +function assignOrWrapInDeprecateGetter (obj, key, value, message) { + if (message) { + utils.defineGetter(obj, key, function () { + console.log(message); + delete obj[key]; + clobber(obj, key, value); + return value; + }); + } else { + clobber(obj, key, value); + } +} + +function include (parent, objects, clobber, merge) { + each(objects, function (obj, key) { + try { + var result = obj.path ? require(obj.path) : {}; + + if (clobber) { + // Clobber if it doesn't exist. + if (typeof parent[key] === 'undefined') { + assignOrWrapInDeprecateGetter(parent, key, result, obj.deprecated); + } else if (typeof obj.path !== 'undefined') { + // If merging, merge properties onto parent, otherwise, clobber. + if (merge) { + recursiveMerge(parent[key], result); + } else { + assignOrWrapInDeprecateGetter(parent, key, result, obj.deprecated); + } + } + result = parent[key]; + } else { + // Overwrite if not currently defined. + if (typeof parent[key] === 'undefined') { + assignOrWrapInDeprecateGetter(parent, key, result, obj.deprecated); + } else { + // Set result to what already exists, so we can build children into it if they exist. + result = parent[key]; + } + } + + if (obj.children) { + include(result, obj.children, clobber, merge); + } + } catch (e) { + utils.alert('Exception building Cordova JS globals: ' + e + ' for key "' + key + '"'); + } + }); +} + +/** + * Merge properties from one object onto another recursively. Properties from + * the src object will overwrite existing target property. + * + * @param target Object to merge properties into. + * @param src Object to merge properties from. + */ +function recursiveMerge (target, src) { + for (var prop in src) { + if (Object.prototype.hasOwnProperty.call(src, prop)) { + if (target.prototype && target.prototype.constructor === target) { + // If the target object is a constructor override off prototype. + clobber(target.prototype, prop, src[prop]); + } else { + if (typeof src[prop] === 'object' && typeof target[prop] === 'object') { + recursiveMerge(target[prop], src[prop]); + } else { + clobber(target, prop, src[prop]); + } + } + } + } +} + +exports.buildIntoButDoNotClobber = function (objects, target) { + include(target, objects, false, false); +}; +exports.buildIntoAndClobber = function (objects, target) { + include(target, objects, true, false); +}; +exports.buildIntoAndMerge = function (objects, target) { + include(target, objects, true, true); +}; +exports.recursiveMerge = recursiveMerge; +exports.assignOrWrapInDeprecateGetter = assignOrWrapInDeprecateGetter; + +}); + +// file: src/common/channel.js +define("cordova/channel", function(require, exports, module) { + +var utils = require('cordova/utils'); +var nextGuid = 1; + +/** + * Custom pub-sub "channel" that can have functions subscribed to it + * This object is used to define and control firing of events for + * cordova initialization, as well as for custom events thereafter. + * + * The order of events during page load and Cordova startup is as follows: + * + * onDOMContentLoaded* Internal event that is received when the web page is loaded and parsed. + * onNativeReady* Internal event that indicates the Cordova native side is ready. + * onCordovaReady* Internal event fired when all Cordova JavaScript objects have been created. + * onDeviceReady* User event fired to indicate that Cordova is ready + * onResume User event fired to indicate a start/resume lifecycle event + * onPause User event fired to indicate a pause lifecycle event + * + * The events marked with an * are sticky. Once they have fired, they will stay in the fired state. + * All listeners that subscribe after the event is fired will be executed right away. + * + * The only Cordova events that user code should register for are: + * deviceready Cordova native code is initialized and Cordova APIs can be called from JavaScript + * pause App has moved to background + * resume App has returned to foreground + * + * Listeners can be registered as: + * document.addEventListener("deviceready", myDeviceReadyListener, false); + * document.addEventListener("resume", myResumeListener, false); + * document.addEventListener("pause", myPauseListener, false); + * + * The DOM lifecycle events should be used for saving and restoring state + * window.onload + * window.onunload + * + */ + +/** + * Channel + * @constructor + * @param type String the channel name + */ +var Channel = function (type, sticky) { + this.type = type; + // Map of guid -> function. + this.handlers = {}; + // 0 = Non-sticky, 1 = Sticky non-fired, 2 = Sticky fired. + this.state = sticky ? 1 : 0; + // Used in sticky mode to remember args passed to fire(). + this.fireArgs = null; + // Used by onHasSubscribersChange to know if there are any listeners. + this.numHandlers = 0; + // Function that is called when the first listener is subscribed, or when + // the last listener is unsubscribed. + this.onHasSubscribersChange = null; +}; +var channel = { + /** + * Calls the provided function only after all of the channels specified + * have been fired. All channels must be sticky channels. + */ + join: function (h, c) { + var len = c.length; + var i = len; + var f = function () { + if (!(--i)) h(); + }; + for (var j = 0; j < len; j++) { + if (c[j].state === 0) { + throw Error('Can only use join with sticky channels.'); + } + c[j].subscribe(f); + } + if (!len) h(); + }, + + create: function (type) { + return (channel[type] = new Channel(type, false)); + }, + createSticky: function (type) { + return (channel[type] = new Channel(type, true)); + }, + + /** + * cordova Channels that must fire before "deviceready" is fired. + */ + deviceReadyChannelsArray: [], + deviceReadyChannelsMap: {}, + + /** + * Indicate that a feature needs to be initialized before it is ready to be used. + * This holds up Cordova's "deviceready" event until the feature has been initialized + * and Cordova.initComplete(feature) is called. + * + * @param feature {String} The unique feature name + */ + waitForInitialization: function (feature) { + if (feature) { + var c = channel[feature] || this.createSticky(feature); + this.deviceReadyChannelsMap[feature] = c; + this.deviceReadyChannelsArray.push(c); + } + }, + + /** + * Indicate that initialization code has completed and the feature is ready to be used. + * + * @param feature {String} The unique feature name + */ + initializationComplete: function (feature) { + var c = this.deviceReadyChannelsMap[feature]; + if (c) { + c.fire(); + } + } +}; + +function checkSubscriptionArgument (argument) { + if (typeof argument !== 'function' && typeof argument.handleEvent !== 'function') { + throw new Error( + 'Must provide a function or an EventListener object ' + + 'implementing the handleEvent interface.' + ); + } +} + +/** + * Subscribes the given function to the channel. Any time that + * Channel.fire is called so too will the function. + * Optionally specify an execution context for the function + * and a guid that can be used to stop subscribing to the channel. + * Returns the guid. + */ +Channel.prototype.subscribe = function (eventListenerOrFunction, eventListener) { + checkSubscriptionArgument(eventListenerOrFunction); + var handleEvent, guid; + + if (eventListenerOrFunction && typeof eventListenerOrFunction === 'object') { + // Received an EventListener object implementing the handleEvent interface + handleEvent = eventListenerOrFunction.handleEvent; + eventListener = eventListenerOrFunction; + } else { + // Received a function to handle event + handleEvent = eventListenerOrFunction; + } + + if (this.state === 2) { + handleEvent.apply(eventListener || this, this.fireArgs); + return; + } + + guid = eventListenerOrFunction.observer_guid; + if (typeof eventListener === 'object') { + handleEvent = utils.close(eventListener, handleEvent); + } + + if (!guid) { + // First time any channel has seen this subscriber + guid = '' + nextGuid++; + } + handleEvent.observer_guid = guid; + eventListenerOrFunction.observer_guid = guid; + + // Don't add the same handler more than once. + if (!this.handlers[guid]) { + this.handlers[guid] = handleEvent; + this.numHandlers++; + if (this.numHandlers === 1) { + this.onHasSubscribersChange && this.onHasSubscribersChange(); + } + } +}; + +/** + * Unsubscribes the function with the given guid from the channel. + */ +Channel.prototype.unsubscribe = function (eventListenerOrFunction) { + checkSubscriptionArgument(eventListenerOrFunction); + var handleEvent, guid, handler; + + if (eventListenerOrFunction && typeof eventListenerOrFunction === 'object') { + // Received an EventListener object implementing the handleEvent interface + handleEvent = eventListenerOrFunction.handleEvent; + } else { + // Received a function to handle event + handleEvent = eventListenerOrFunction; + } + + guid = handleEvent.observer_guid; + handler = this.handlers[guid]; + if (handler) { + delete this.handlers[guid]; + this.numHandlers--; + if (this.numHandlers === 0) { + this.onHasSubscribersChange && this.onHasSubscribersChange(); + } + } +}; + +/** + * Calls all functions subscribed to this channel. + */ +Channel.prototype.fire = function (e) { + var fireArgs = Array.prototype.slice.call(arguments); + // Apply stickiness. + if (this.state === 1) { + this.state = 2; + this.fireArgs = fireArgs; + } + if (this.numHandlers) { + // Copy the values first so that it is safe to modify it from within + // callbacks. + var toCall = []; + for (var item in this.handlers) { + toCall.push(this.handlers[item]); + } + for (var i = 0; i < toCall.length; ++i) { + toCall[i].apply(this, fireArgs); + } + if (this.state === 2 && this.numHandlers) { + this.numHandlers = 0; + this.handlers = {}; + this.onHasSubscribersChange && this.onHasSubscribersChange(); + } + } +}; + +// defining them here so they are ready super fast! +// DOM event that is received when the web page is loaded and parsed. +channel.createSticky('onDOMContentLoaded'); + +// Event to indicate the Cordova native side is ready. +channel.createSticky('onNativeReady'); + +// Event to indicate that all Cordova JavaScript objects have been created +// and it's time to run plugin constructors. +channel.createSticky('onCordovaReady'); + +// Event to indicate that all automatically loaded JS plugins are loaded and ready. +// FIXME remove this +channel.createSticky('onPluginsReady'); + +// Event to indicate that Cordova is ready +channel.createSticky('onDeviceReady'); + +// Event to indicate a resume lifecycle event +channel.create('onResume'); + +// Event to indicate a pause lifecycle event +channel.create('onPause'); + +// Channels that must fire before "deviceready" is fired. +channel.waitForInitialization('onCordovaReady'); +channel.waitForInitialization('onDOMContentLoaded'); + +module.exports = channel; + +}); + +// file: ../../cordova-js-src/exec.js +define("cordova/exec", function(require, exports, module) { + +/** + * Creates the exec bridge used to notify the native code of + * commands. + */ +var cordova = require('cordova'); +var utils = require('cordova/utils'); +var base64 = require('cordova/base64'); + +function massageArgsJsToNative (args) { + if (!args || utils.typeName(args) !== 'Array') { + return args; + } + var ret = []; + args.forEach(function (arg, i) { + if (utils.typeName(arg) === 'ArrayBuffer') { + ret.push({ + CDVType: 'ArrayBuffer', + data: base64.fromArrayBuffer(arg) + }); + } else { + ret.push(arg); + } + }); + return ret; +} + +function massageMessageNativeToJs (message) { + if (message.CDVType === 'ArrayBuffer') { + var stringToArrayBuffer = function (str) { + var ret = new Uint8Array(str.length); + for (var i = 0; i < str.length; i++) { + ret[i] = str.charCodeAt(i); + } + return ret.buffer; + }; + var base64ToArrayBuffer = function (b64) { + return stringToArrayBuffer(atob(b64)); + }; + message = base64ToArrayBuffer(message.data); + } + return message; +} + +function convertMessageToArgsNativeToJs (message) { + var args = []; + if (!message || !Object.prototype.hasOwnProperty.call(message, 'CDVType')) { + args.push(message); + } else if (message.CDVType === 'MultiPart') { + message.messages.forEach(function (e) { + args.push(massageMessageNativeToJs(e)); + }); + } else { + args.push(massageMessageNativeToJs(message)); + } + return args; +} + +var iOSExec = function () { + var successCallback, failCallback, service, action, actionArgs; + var callbackId = null; + if (typeof arguments[0] !== 'string') { + // FORMAT ONE + successCallback = arguments[0]; + failCallback = arguments[1]; + service = arguments[2]; + action = arguments[3]; + actionArgs = arguments[4]; + + // Since we need to maintain backwards compatibility, we have to pass + // an invalid callbackId even if no callback was provided since plugins + // will be expecting it. The Cordova.exec() implementation allocates + // an invalid callbackId and passes it even if no callbacks were given. + callbackId = 'INVALID'; + } else { + throw new Error('The old format of this exec call has been removed (deprecated since 2.1). Change to: ' + // eslint-disable-line + 'cordova.exec(null, null, \'Service\', \'action\', [ arg1, arg2 ]);'); + } + + // If actionArgs is not provided, default to an empty array + actionArgs = actionArgs || []; + + // Register the callbacks and add the callbackId to the positional + // arguments if given. + if (successCallback || failCallback) { + callbackId = service + cordova.callbackId++; + cordova.callbacks[callbackId] = + { success: successCallback, fail: failCallback }; + } + + actionArgs = massageArgsJsToNative(actionArgs); + + // CB-10133 DataClone DOM Exception 25 guard (fast function remover) + var command = [callbackId, service, action, JSON.parse(JSON.stringify(actionArgs))]; + window.webkit.messageHandlers.cordova.postMessage(command); +}; + +iOSExec.nativeCallback = function (callbackId, status, message, keepCallback, debug) { + var success = status === 0 || status === 1; + var args = convertMessageToArgsNativeToJs(message); + Promise.resolve().then(function () { + cordova.callbackFromNative(callbackId, success, status, args, keepCallback); + }); +}; + +// for backwards compatibility +iOSExec.nativeEvalAndFetch = function (func) { + try { + func(); + } catch (e) { + console.log(e); + } +}; + +// Proxy the exec for bridge changes. See CB-10106 + +function cordovaExec () { + var cexec = require('cordova/exec'); + var cexec_valid = (typeof cexec.nativeFetchMessages === 'function') && (typeof cexec.nativeEvalAndFetch === 'function') && (typeof cexec.nativeCallback === 'function'); + return (cexec_valid && execProxy !== cexec) ? cexec : iOSExec; +} + +function execProxy () { + cordovaExec().apply(null, arguments); +} + +execProxy.nativeFetchMessages = function () { + return cordovaExec().nativeFetchMessages.apply(null, arguments); +}; + +execProxy.nativeEvalAndFetch = function () { + return cordovaExec().nativeEvalAndFetch.apply(null, arguments); +}; + +execProxy.nativeCallback = function () { + return cordovaExec().nativeCallback.apply(null, arguments); +}; + +module.exports = execProxy; + +}); + +// file: src/common/exec/proxy.js +define("cordova/exec/proxy", function(require, exports, module) { + +// internal map of proxy function +var CommandProxyMap = {}; + +module.exports = { + + // example: cordova.commandProxy.add("Accelerometer",{getCurrentAcceleration: function(successCallback, errorCallback, options) {...},...); + add: function (id, proxyObj) { + console.log('adding proxy for ' + id); + CommandProxyMap[id] = proxyObj; + return proxyObj; + }, + + // cordova.commandProxy.remove("Accelerometer"); + remove: function (id) { + var proxy = CommandProxyMap[id]; + delete CommandProxyMap[id]; + CommandProxyMap[id] = null; + return proxy; + }, + + get: function (service, action) { + return (CommandProxyMap[service] ? CommandProxyMap[service][action] : null); + } +}; + +}); + +// file: src/common/init.js +define("cordova/init", function(require, exports, module) { + +var channel = require('cordova/channel'); +var cordova = require('cordova'); +var modulemapper = require('cordova/modulemapper'); +var platform = require('cordova/platform'); +var pluginloader = require('cordova/pluginloader'); + +var platformInitChannelsArray = [channel.onNativeReady, channel.onPluginsReady]; + +function logUnfiredChannels (arr) { + for (var i = 0; i < arr.length; ++i) { + if (arr[i].state !== 2) { + console.log('Channel not fired: ' + arr[i].type); + } + } +} + +window.setTimeout(function () { + if (channel.onDeviceReady.state !== 2) { + console.log('deviceready has not fired after 5 seconds.'); + logUnfiredChannels(platformInitChannelsArray); + logUnfiredChannels(channel.deviceReadyChannelsArray); + } +}, 5000); + +if (!window.console) { + window.console = { + log: function () {} + }; +} +if (!window.console.warn) { + window.console.warn = function (msg) { + this.log('warn: ' + msg); + }; +} + +// Register pause, resume and deviceready channels as events on document. +channel.onPause = cordova.addDocumentEventHandler('pause'); +channel.onResume = cordova.addDocumentEventHandler('resume'); +channel.onActivated = cordova.addDocumentEventHandler('activated'); +channel.onDeviceReady = cordova.addStickyDocumentEventHandler('deviceready'); + +// Listen for DOMContentLoaded and notify our channel subscribers. +if (document.readyState === 'complete' || document.readyState === 'interactive') { + channel.onDOMContentLoaded.fire(); +} else { + document.addEventListener('DOMContentLoaded', function () { + channel.onDOMContentLoaded.fire(); + }, false); +} + +// _nativeReady is global variable that the native side can set +// to signify that the native code is ready. It is a global since +// it may be called before any cordova JS is ready. +if (window._nativeReady) { + channel.onNativeReady.fire(); +} + +modulemapper.clobbers('cordova', 'cordova'); +modulemapper.clobbers('cordova/exec', 'cordova.exec'); +modulemapper.clobbers('cordova/exec', 'Cordova.exec'); + +// Call the platform-specific initialization. +platform.bootstrap && platform.bootstrap(); + +// Wrap in a setTimeout to support the use-case of having plugin JS appended to cordova.js. +// The delay allows the attached modules to be defined before the plugin loader looks for them. +setTimeout(function () { + pluginloader.load(function () { + channel.onPluginsReady.fire(); + }); +}, 0); + +/** + * Create all cordova objects once native side is ready. + */ +channel.join(function () { + modulemapper.mapModules(window); + + platform.initialize && platform.initialize(); + + // Fire event to notify that all objects are created + channel.onCordovaReady.fire(); + + // Fire onDeviceReady event once page has fully loaded, all + // constructors have run and cordova info has been received from native + // side. + channel.join(function () { + require('cordova').fireDocumentEvent('deviceready'); + }, channel.deviceReadyChannelsArray); +}, platformInitChannelsArray); + +}); + +// file: src/common/modulemapper.js +define("cordova/modulemapper", function(require, exports, module) { + +var builder = require('cordova/builder'); +var moduleMap = define.moduleMap; +var symbolList; +var deprecationMap; + +exports.reset = function () { + symbolList = []; + deprecationMap = {}; +}; + +function addEntry (strategy, moduleName, symbolPath, opt_deprecationMessage) { + if (!(moduleName in moduleMap)) { + throw new Error('Module ' + moduleName + ' does not exist.'); + } + symbolList.push(strategy, moduleName, symbolPath); + if (opt_deprecationMessage) { + deprecationMap[symbolPath] = opt_deprecationMessage; + } +} + +// Note: Android 2.3 does have Function.bind(). +exports.clobbers = function (moduleName, symbolPath, opt_deprecationMessage) { + addEntry('c', moduleName, symbolPath, opt_deprecationMessage); +}; + +exports.merges = function (moduleName, symbolPath, opt_deprecationMessage) { + addEntry('m', moduleName, symbolPath, opt_deprecationMessage); +}; + +exports.defaults = function (moduleName, symbolPath, opt_deprecationMessage) { + addEntry('d', moduleName, symbolPath, opt_deprecationMessage); +}; + +exports.runs = function (moduleName) { + addEntry('r', moduleName, null); +}; + +function prepareNamespace (symbolPath, context) { + if (!symbolPath) { + return context; + } + return symbolPath.split('.').reduce(function (cur, part) { + return (cur[part] = cur[part] || {}); + }, context); +} + +exports.mapModules = function (context) { + var origSymbols = {}; + context.CDV_origSymbols = origSymbols; + for (var i = 0, len = symbolList.length; i < len; i += 3) { + var strategy = symbolList[i]; + var moduleName = symbolList[i + 1]; + var module = require(moduleName); + // + if (strategy === 'r') { + continue; + } + var symbolPath = symbolList[i + 2]; + var lastDot = symbolPath.lastIndexOf('.'); + var namespace = symbolPath.substr(0, lastDot); + var lastName = symbolPath.substr(lastDot + 1); + + var deprecationMsg = symbolPath in deprecationMap ? 'Access made to deprecated symbol: ' + symbolPath + '. ' + deprecationMsg : null; + var parentObj = prepareNamespace(namespace, context); + var target = parentObj[lastName]; + + if (strategy === 'm' && target) { + builder.recursiveMerge(target, module); + } else if ((strategy === 'd' && !target) || (strategy !== 'd')) { + if (!(symbolPath in origSymbols)) { + origSymbols[symbolPath] = target; + } + builder.assignOrWrapInDeprecateGetter(parentObj, lastName, module, deprecationMsg); + } + } +}; + +exports.getOriginalSymbol = function (context, symbolPath) { + var origSymbols = context.CDV_origSymbols; + if (origSymbols && (symbolPath in origSymbols)) { + return origSymbols[symbolPath]; + } + var parts = symbolPath.split('.'); + var obj = context; + for (var i = 0; i < parts.length; ++i) { + obj = obj && obj[parts[i]]; + } + return obj; +}; + +exports.reset(); + +}); + +// file: ../../cordova-js-src/platform.js +define("cordova/platform", function(require, exports, module) { + +module.exports = { + id: 'ios', + bootstrap: function () { + // Attach the console polyfill that is iOS-only to window.console + // see the file under plugin/ios/console.js + require('cordova/modulemapper').clobbers('cordova/plugin/ios/console', 'window.console'); + + // Attach the wkwebkit utility to window.WkWebView + // see the file under plugin/ios/wkwebkit.js + require('cordova/modulemapper').clobbers('cordova/plugin/ios/wkwebkit', 'window.WkWebView'); + + // Attach the splashscreen utility to window.navigator.splashscreen + // see the file under plugin/ios/launchscreen.js + require('cordova/modulemapper').clobbers('cordova/plugin/ios/launchscreen', 'navigator.splashscreen'); + + // Attach the internal statusBar utility to window.statusbar + // see the file under plugin/ios/statusbar.js + require('cordova/modulemapper').clobbers('cordova/plugin/ios/statusbar', 'window.statusbar'); + + require('cordova/channel').onNativeReady.fire(); + } +}; + +}); + +// file: ../../cordova-js-src/plugin/ios/console.js +define("cordova/plugin/ios/console", function(require, exports, module) { + +// ------------------------------------------------------------------------------ + +var logger = require('cordova/plugin/ios/logger'); + +// ------------------------------------------------------------------------------ +// object that we're exporting +// ------------------------------------------------------------------------------ +var console = module.exports; + +// ------------------------------------------------------------------------------ +// copy of the original console object +// ------------------------------------------------------------------------------ +var WinConsole = window.console; + +// ------------------------------------------------------------------------------ +// whether to use the logger +// ------------------------------------------------------------------------------ +var UseLogger = false; + +// ------------------------------------------------------------------------------ +// Timers +// ------------------------------------------------------------------------------ +var Timers = {}; + +// ------------------------------------------------------------------------------ +// used for unimplemented methods +// ------------------------------------------------------------------------------ +function noop () {} + +// ------------------------------------------------------------------------------ +// used for unimplemented methods +// ------------------------------------------------------------------------------ +console.useLogger = function (value) { + if (arguments.length) UseLogger = !!value; + + if (UseLogger) { + if (logger.useConsole()) { + throw new Error('console and logger are too intertwingly'); + } + } + + return UseLogger; +}; + +// ------------------------------------------------------------------------------ +console.log = function () { + if (logger.useConsole()) return; + logger.log.apply(logger, [].slice.call(arguments)); +}; + +// ------------------------------------------------------------------------------ +console.error = function () { + if (logger.useConsole()) return; + logger.error.apply(logger, [].slice.call(arguments)); +}; + +// ------------------------------------------------------------------------------ +console.warn = function () { + if (logger.useConsole()) return; + logger.warn.apply(logger, [].slice.call(arguments)); +}; + +// ------------------------------------------------------------------------------ +console.info = function () { + if (logger.useConsole()) return; + logger.info.apply(logger, [].slice.call(arguments)); +}; + +// ------------------------------------------------------------------------------ +console.debug = function () { + if (logger.useConsole()) return; + logger.debug.apply(logger, [].slice.call(arguments)); +}; + +// ------------------------------------------------------------------------------ +console.assert = function (expression) { + if (expression) return; + + var message = logger.format.apply(logger.format, [].slice.call(arguments, 1)); + console.log('ASSERT: ' + message); +}; + +// ------------------------------------------------------------------------------ +console.clear = function () {}; + +// ------------------------------------------------------------------------------ +console.dir = function (object) { + console.log('%o', object); +}; + +// ------------------------------------------------------------------------------ +console.dirxml = function (node) { + console.log(node.innerHTML); +}; + +// ------------------------------------------------------------------------------ +console.trace = noop; + +// ------------------------------------------------------------------------------ +console.group = console.log; + +// ------------------------------------------------------------------------------ +console.groupCollapsed = console.log; + +// ------------------------------------------------------------------------------ +console.groupEnd = noop; + +// ------------------------------------------------------------------------------ +console.time = function (name) { + Timers[name] = new Date().valueOf(); +}; + +// ------------------------------------------------------------------------------ +console.timeEnd = function (name) { + var timeStart = Timers[name]; + if (!timeStart) { + console.warn('unknown timer: ' + name); + return; + } + + var timeElapsed = new Date().valueOf() - timeStart; + console.log(name + ': ' + timeElapsed + 'ms'); +}; + +// ------------------------------------------------------------------------------ +console.timeStamp = noop; + +// ------------------------------------------------------------------------------ +console.profile = noop; + +// ------------------------------------------------------------------------------ +console.profileEnd = noop; + +// ------------------------------------------------------------------------------ +console.count = noop; + +// ------------------------------------------------------------------------------ +console.exception = console.log; + +// ------------------------------------------------------------------------------ +console.table = function (data, columns) { + console.log('%o', data); +}; + +// ------------------------------------------------------------------------------ +// return a new function that calls both functions passed as args +// ------------------------------------------------------------------------------ +function wrappedOrigCall (orgFunc, newFunc) { + return function () { + var args = [].slice.call(arguments); + try { orgFunc.apply(WinConsole, args); } catch (e) {} + try { newFunc.apply(console, args); } catch (e) {} + }; +} + +// ------------------------------------------------------------------------------ +// For every function that exists in the original console object, that +// also exists in the new console object, wrap the new console method +// with one that calls both +// ------------------------------------------------------------------------------ +for (var key in console) { + if (typeof WinConsole[key] === 'function') { + console[key] = wrappedOrigCall(WinConsole[key], console[key]); + } +} + +}); + +// file: ../../cordova-js-src/plugin/ios/launchscreen.js +define("cordova/plugin/ios/launchscreen", function(require, exports, module) { + +var exec = require('cordova/exec'); + +var launchscreen = { + show: function () { + exec(null, null, 'LaunchScreen', 'show', []); + }, + hide: function () { + exec(null, null, 'LaunchScreen', 'hide', []); + } +}; + +module.exports = launchscreen; + +}); + +// file: ../../cordova-js-src/plugin/ios/logger.js +define("cordova/plugin/ios/logger", function(require, exports, module) { + +// ------------------------------------------------------------------------------ +// The logger module exports the following properties/functions: +// +// LOG - constant for the level LOG +// ERROR - constant for the level ERROR +// WARN - constant for the level WARN +// INFO - constant for the level INFO +// DEBUG - constant for the level DEBUG +// logLevel() - returns current log level +// logLevel(value) - sets and returns a new log level +// useConsole() - returns whether logger is using console +// useConsole(value) - sets and returns whether logger is using console +// log(message,...) - logs a message at level LOG +// error(message,...) - logs a message at level ERROR +// warn(message,...) - logs a message at level WARN +// info(message,...) - logs a message at level INFO +// debug(message,...) - logs a message at level DEBUG +// logLevel(level,message,...) - logs a message specified level +// +// ------------------------------------------------------------------------------ + +var logger = exports; + +var exec = require('cordova/exec'); + +var UseConsole = false; +var UseLogger = true; +var Queued = []; +var DeviceReady = false; +var CurrentLevel; + +var originalConsole = console; + +/** + * Logging levels + */ + +var Levels = [ + 'LOG', + 'ERROR', + 'WARN', + 'INFO', + 'DEBUG' +]; + +/* + * add the logging levels to the logger object and + * to a separate levelsMap object for testing + */ + +var LevelsMap = {}; +for (var i = 0; i < Levels.length; i++) { + var level = Levels[i]; + LevelsMap[level] = i; + logger[level] = level; +} + +CurrentLevel = LevelsMap.WARN; + +/** + * Getter/Setter for the logging level + * + * Returns the current logging level. + * + * When a value is passed, sets the logging level to that value. + * The values should be one of the following constants: + * logger.LOG + * logger.ERROR + * logger.WARN + * logger.INFO + * logger.DEBUG + * + * The value used determines which messages get printed. The logging + * values above are in order, and only messages logged at the logging + * level or above will actually be displayed to the user. E.g., the + * default level is WARN, so only messages logged with LOG, ERROR, or + * WARN will be displayed; INFO and DEBUG messages will be ignored. + */ +logger.level = function (value) { + if (arguments.length) { + if (LevelsMap[value] === null) { + throw new Error('invalid logging level: ' + value); + } + CurrentLevel = LevelsMap[value]; + } + + return Levels[CurrentLevel]; +}; + +/** + * Getter/Setter for the useConsole functionality + * + * When useConsole is true, the logger will log via the + * browser 'console' object. + */ +logger.useConsole = function (value) { + if (arguments.length) UseConsole = !!value; + + if (UseConsole) { + if (typeof console === 'undefined') { + throw new Error('global console object is not defined'); + } + + if (typeof console.log !== 'function') { + throw new Error('global console object does not have a log function'); + } + + if (typeof console.useLogger === 'function') { + if (console.useLogger()) { + throw new Error('console and logger are too intertwingly'); + } + } + } + + return UseConsole; +}; + +/** + * Getter/Setter for the useLogger functionality + * + * When useLogger is true, the logger will log via the + * native Logger plugin. + */ +logger.useLogger = function (value) { + // Enforce boolean + if (arguments.length) UseLogger = !!value; + return UseLogger; +}; + +/** + * Logs a message at the LOG level. + * + * Parameters passed after message are used applied to + * the message with utils.format() + */ +logger.log = function (message) { logWithArgs('LOG', arguments); }; + +/** + * Logs a message at the ERROR level. + * + * Parameters passed after message are used applied to + * the message with utils.format() + */ +logger.error = function (message) { logWithArgs('ERROR', arguments); }; + +/** + * Logs a message at the WARN level. + * + * Parameters passed after message are used applied to + * the message with utils.format() + */ +logger.warn = function (message) { logWithArgs('WARN', arguments); }; + +/** + * Logs a message at the INFO level. + * + * Parameters passed after message are used applied to + * the message with utils.format() + */ +logger.info = function (message) { logWithArgs('INFO', arguments); }; + +/** + * Logs a message at the DEBUG level. + * + * Parameters passed after message are used applied to + * the message with utils.format() + */ +logger.debug = function (message) { logWithArgs('DEBUG', arguments); }; + +// log at the specified level with args +function logWithArgs (level, args) { + args = [level].concat([].slice.call(args)); + logger.logLevel.apply(logger, args); +} + +// return the correct formatString for an object +function formatStringForMessage (message) { + return (typeof message === 'string') ? '' : '%o'; +} + +/** + * Logs a message at the specified level. + * + * Parameters passed after message are used applied to + * the message with utils.format() + */ +logger.logLevel = function (level /* , ... */) { + // format the message with the parameters + var formatArgs = [].slice.call(arguments, 1); + var fmtString = formatStringForMessage(formatArgs[0]); + if (fmtString.length > 0) { + formatArgs.unshift(fmtString); // add formatString + } + + var message = logger.format.apply(logger.format, formatArgs); + + if (LevelsMap[level] === null) { + throw new Error('invalid logging level: ' + level); + } + + if (LevelsMap[level] > CurrentLevel) return; + + // queue the message if not yet at deviceready + if (!DeviceReady && !UseConsole) { + Queued.push([level, message]); + return; + } + + // Log using the native logger if that is enabled + if (UseLogger) { + exec(null, null, 'Console', 'logLevel', [level, message]); + } + + // Log using the console if that is enabled + if (UseConsole) { + // make sure console is not using logger + if (console.useLogger()) { + throw new Error('console and logger are too intertwingly'); + } + + // log to the console + switch (level) { + case logger.LOG: originalConsole.log(message); break; + case logger.ERROR: originalConsole.log('ERROR: ' + message); break; + case logger.WARN: originalConsole.log('WARN: ' + message); break; + case logger.INFO: originalConsole.log('INFO: ' + message); break; + case logger.DEBUG: originalConsole.log('DEBUG: ' + message); break; + } + } +}; + +/** + * Formats a string and arguments following it ala console.log() + * + * Any remaining arguments will be appended to the formatted string. + * + * for rationale, see FireBug's Console API: + * http://getfirebug.com/wiki/index.php/Console_API + */ +logger.format = function (formatString, args) { + return __format(arguments[0], [].slice.call(arguments, 1)).join(' '); +}; + +// ------------------------------------------------------------------------------ +/** + * Formats a string and arguments following it ala vsprintf() + * + * format chars: + * %j - format arg as JSON + * %o - format arg as JSON + * %c - format arg as '' + * %% - replace with '%' + * any other char following % will format it's + * arg via toString(). + * + * Returns an array containing the formatted string and any remaining + * arguments. + */ +function __format (formatString, args) { + if (formatString === null || formatString === undefined) return ['']; + if (arguments.length === 1) return [formatString.toString()]; + + if (typeof formatString !== 'string') { formatString = formatString.toString(); } + + var pattern = /(.*?)%(.)(.*)/; + var rest = formatString; + var result = []; + + while (args.length) { + var match = pattern.exec(rest); + if (!match) break; + + var arg = args.shift(); + rest = match[3]; + result.push(match[1]); + + if (match[2] === '%') { + result.push('%'); + args.unshift(arg); + continue; + } + + result.push(__formatted(arg, match[2])); + } + + result.push(rest); + + var remainingArgs = [].slice.call(args); + remainingArgs.unshift(result.join('')); + return remainingArgs; +} + +function __formatted (object, formatChar) { + try { + switch (formatChar) { + case 'j': + case 'o': return JSON.stringify(object); + case 'c': return ''; + } + } catch (e) { + return 'error JSON.stringify()ing argument: ' + e; + } + + if ((object === null) || (object === undefined)) { + return Object.prototype.toString.call(object); + } + + return object.toString(); +} + +// ------------------------------------------------------------------------------ +// when deviceready fires, log queued messages +logger.__onDeviceReady = function () { + if (DeviceReady) return; + + DeviceReady = true; + + for (var i = 0; i < Queued.length; i++) { + var messageArgs = Queued[i]; + logger.logLevel(messageArgs[0], messageArgs[1]); + } + + Queued = null; +}; + +// add a deviceready event to log queued messages +document.addEventListener('deviceready', logger.__onDeviceReady, false); + +}); + +// file: ../../cordova-js-src/plugin/ios/statusbar.js +define("cordova/plugin/ios/statusbar", function(require, exports, module) { + +var exec = require('cordova/exec'); + +var statusBarVisible = true; +var statusBar = {}; + +// This