Repository: microsoft/react-native-code-push
Branch: master
Commit: 50a7ed5bc0f1
Files: 389
Total size: 2.2 MB
Directory structure:
gitextract_eag1d8bl/
├── .azurepipelines/
│ ├── build-rn-code-push-1es.yml
│ └── test-rn-code-push.yml
├── .config/
│ └── CredScanSuppressions.json
├── .github/
│ ├── CODEOWNERS
│ ├── ISSUE_TEMPLATE.md
│ └── policies/
│ └── resourceManagement.yml
├── .gitignore
├── .npmignore
├── .vscode/
│ ├── launch.json
│ ├── settings.json
│ └── tasks.json
├── .watchmanconfig
├── AlertAdapter.js
├── CONTRIBUTING.md
├── CodePush.js
├── CodePush.podspec
├── Examples/
│ ├── CodePushDemoApp/
│ │ ├── .buckconfig
│ │ ├── .editorconfig
│ │ ├── .eslintrc.js
│ │ ├── .flowconfig
│ │ ├── .gitattributes
│ │ ├── .gitignore
│ │ ├── .prettierrc.js
│ │ ├── .watchmanconfig
│ │ ├── App.js
│ │ ├── __tests__/
│ │ │ └── App-test.js
│ │ ├── android/
│ │ │ ├── app/
│ │ │ │ ├── BUCK
│ │ │ │ ├── build.gradle
│ │ │ │ ├── build_defs.bzl
│ │ │ │ ├── debug.keystore
│ │ │ │ ├── proguard-rules.pro
│ │ │ │ └── src/
│ │ │ │ └── main/
│ │ │ │ ├── AndroidManifest.xml
│ │ │ │ ├── java/
│ │ │ │ │ └── com/
│ │ │ │ │ └── codepushdemoapp/
│ │ │ │ │ ├── MainActivity.java
│ │ │ │ │ └── MainApplication.java
│ │ │ │ └── res/
│ │ │ │ └── values/
│ │ │ │ ├── strings.xml
│ │ │ │ └── styles.xml
│ │ │ ├── build.gradle
│ │ │ ├── gradle/
│ │ │ │ └── wrapper/
│ │ │ │ ├── gradle-wrapper.jar
│ │ │ │ └── gradle-wrapper.properties
│ │ │ ├── gradle.properties
│ │ │ ├── gradlew
│ │ │ ├── gradlew.bat
│ │ │ └── settings.gradle
│ │ ├── app.json
│ │ ├── babel.config.js
│ │ ├── index.js
│ │ ├── ios/
│ │ │ ├── CodePushDemoApp/
│ │ │ │ ├── AppDelegate.h
│ │ │ │ ├── AppDelegate.m
│ │ │ │ ├── Images.xcassets/
│ │ │ │ │ ├── AppIcon.appiconset/
│ │ │ │ │ │ └── Contents.json
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── Info.plist
│ │ │ │ ├── LaunchScreen.storyboard
│ │ │ │ └── main.m
│ │ │ ├── CodePushDemoApp.xcodeproj/
│ │ │ │ ├── project.pbxproj
│ │ │ │ └── xcshareddata/
│ │ │ │ └── xcschemes/
│ │ │ │ └── CodePushDemoApp.xcscheme
│ │ │ ├── CodePushDemoApp.xcworkspace/
│ │ │ │ ├── contents.xcworkspacedata
│ │ │ │ └── xcshareddata/
│ │ │ │ └── IDEWorkspaceChecks.plist
│ │ │ ├── CodePushDemoAppTests/
│ │ │ │ ├── CodePushDemoAppTests.m
│ │ │ │ └── Info.plist
│ │ │ └── Podfile
│ │ ├── metro.config.js
│ │ └── package.json
│ ├── CodePushDemoAppCpp/
│ │ ├── .buckconfig
│ │ ├── .eslintrc.js
│ │ ├── .flowconfig
│ │ ├── .gitattributes
│ │ ├── .gitignore
│ │ ├── .prettierrc.js
│ │ ├── .watchmanconfig
│ │ ├── App.js
│ │ ├── app.json
│ │ ├── babel.config.js
│ │ ├── index.js
│ │ ├── metro.config.js
│ │ ├── package.json
│ │ └── windows/
│ │ ├── .gitignore
│ │ ├── CodePushDemoAppCpp/
│ │ │ ├── .gitignore
│ │ │ ├── App.cpp
│ │ │ ├── App.h
│ │ │ ├── App.idl
│ │ │ ├── App.xaml
│ │ │ ├── AutolinkedNativeModules.g.cpp
│ │ │ ├── AutolinkedNativeModules.g.h
│ │ │ ├── AutolinkedNativeModules.g.targets
│ │ │ ├── CodePushDemoAppCpp.vcxproj
│ │ │ ├── CodePushDemoAppCpp.vcxproj.filters
│ │ │ ├── CodePushDemoAppCpp_TemporaryKey.pfx
│ │ │ ├── MainPage.cpp
│ │ │ ├── MainPage.h
│ │ │ ├── MainPage.idl
│ │ │ ├── MainPage.xaml
│ │ │ ├── Package.appxmanifest
│ │ │ ├── PropertySheet.props
│ │ │ ├── ReactPackageProvider.cpp
│ │ │ ├── ReactPackageProvider.h
│ │ │ ├── packages.config
│ │ │ ├── pch.cpp
│ │ │ └── pch.h
│ │ └── CodePushDemoAppCpp.sln
│ ├── create-app.js
│ └── nexpect.js
├── LICENSE.md
├── README.md
├── Recipes/
│ ├── UpdateButton.ios.js
│ └── UpdateOnStart.ios.js
├── SECURITY.md
├── android/
│ ├── app/
│ │ ├── build.gradle
│ │ ├── proguard-rules.pro
│ │ └── src/
│ │ ├── debug/
│ │ │ └── AndroidManifest.xml
│ │ └── main/
│ │ ├── AndroidManifest.xml
│ │ └── java/
│ │ └── com/
│ │ └── microsoft/
│ │ └── codepush/
│ │ └── react/
│ │ ├── CodePush.java
│ │ ├── CodePushBuilder.java
│ │ ├── CodePushConstants.java
│ │ ├── CodePushDialog.java
│ │ ├── CodePushInstallMode.java
│ │ ├── CodePushInvalidPublicKeyException.java
│ │ ├── CodePushInvalidUpdateException.java
│ │ ├── CodePushMalformedDataException.java
│ │ ├── CodePushNativeModule.java
│ │ ├── CodePushNotInitializedException.java
│ │ ├── CodePushTelemetryManager.java
│ │ ├── CodePushUnknownException.java
│ │ ├── CodePushUpdateManager.java
│ │ ├── CodePushUpdateState.java
│ │ ├── CodePushUpdateUtils.java
│ │ ├── CodePushUtils.java
│ │ ├── DownloadProgress.java
│ │ ├── DownloadProgressCallback.java
│ │ ├── FileUtils.java
│ │ ├── ReactInstanceHolder.java
│ │ ├── SettingsManager.java
│ │ └── TLSSocketFactory.java
│ ├── build.gradle
│ ├── codepush.gradle
│ ├── gradle/
│ │ └── wrapper/
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
│ ├── gradle.properties
│ ├── gradlew
│ ├── gradlew.bat
│ └── settings.gradle
├── code-push-plugin-testing-framework/
│ ├── package.json
│ ├── script/
│ │ ├── index.js
│ │ ├── platform.js
│ │ ├── projectManager.js
│ │ ├── serverUtil.js
│ │ ├── test.js
│ │ ├── testBuilder.js
│ │ ├── testConfig.js
│ │ └── testUtil.js
│ └── typings/
│ └── code-push-plugin-testing-framework.d.ts
├── docs/
│ ├── api-android.md
│ ├── api-ios.md
│ ├── api-js.md
│ ├── multi-deployment-testing-android.md
│ ├── multi-deployment-testing-ios.md
│ ├── setup-android.md
│ ├── setup-ios.md
│ └── setup-windows.md
├── ios/
│ ├── CodePush/
│ │ ├── Base64/
│ │ │ ├── Base64/
│ │ │ │ ├── MF_Base64Additions.h
│ │ │ │ └── MF_Base64Additions.m
│ │ │ └── README.md
│ │ ├── CodePush.h
│ │ ├── CodePush.m
│ │ ├── CodePushConfig.m
│ │ ├── CodePushDownloadHandler.m
│ │ ├── CodePushErrorUtils.m
│ │ ├── CodePushPackage.m
│ │ ├── CodePushTelemetryManager.m
│ │ ├── CodePushUpdateUtils.m
│ │ ├── CodePushUtils.m
│ │ ├── JWT/
│ │ │ ├── Core/
│ │ │ │ ├── Algorithms/
│ │ │ │ │ ├── Base/
│ │ │ │ │ │ ├── JWTAlgorithm.h
│ │ │ │ │ │ ├── JWTAlgorithmFactory.h
│ │ │ │ │ │ ├── JWTAlgorithmFactory.m
│ │ │ │ │ │ ├── JWTAlgorithmNone.h
│ │ │ │ │ │ └── JWTAlgorithmNone.m
│ │ │ │ │ ├── ESFamily/
│ │ │ │ │ │ ├── JWTAlgorithmESBase.h
│ │ │ │ │ │ └── JWTAlgorithmESBase.m
│ │ │ │ │ ├── HSFamily/
│ │ │ │ │ │ ├── JWTAlgorithmHSBase.h
│ │ │ │ │ │ └── JWTAlgorithmHSBase.m
│ │ │ │ │ ├── Holders/
│ │ │ │ │ │ ├── JWTAlgorithmDataHolder.h
│ │ │ │ │ │ ├── JWTAlgorithmDataHolder.m
│ │ │ │ │ │ ├── JWTAlgorithmDataHolderChain.h
│ │ │ │ │ │ └── JWTAlgorithmDataHolderChain.m
│ │ │ │ │ └── RSFamily/
│ │ │ │ │ ├── JWTAlgorithmRSBase.h
│ │ │ │ │ ├── JWTAlgorithmRSBase.m
│ │ │ │ │ ├── JWTRSAlgorithm.h
│ │ │ │ │ └── RSKeys/
│ │ │ │ │ ├── JWTCryptoKey.h
│ │ │ │ │ ├── JWTCryptoKey.m
│ │ │ │ │ ├── JWTCryptoKeyExtractor.h
│ │ │ │ │ ├── JWTCryptoKeyExtractor.m
│ │ │ │ │ ├── JWTCryptoSecurity.h
│ │ │ │ │ └── JWTCryptoSecurity.m
│ │ │ │ ├── ClaimSet/
│ │ │ │ │ ├── JWTClaim.h
│ │ │ │ │ ├── JWTClaim.m
│ │ │ │ │ ├── JWTClaimsSet.h
│ │ │ │ │ ├── JWTClaimsSet.m
│ │ │ │ │ ├── JWTClaimsSetSerializer.h
│ │ │ │ │ ├── JWTClaimsSetSerializer.m
│ │ │ │ │ ├── JWTClaimsSetVerifier.h
│ │ │ │ │ └── JWTClaimsSetVerifier.m
│ │ │ │ ├── Coding/
│ │ │ │ │ ├── JWTCoding+ResultTypes.h
│ │ │ │ │ ├── JWTCoding+ResultTypes.m
│ │ │ │ │ ├── JWTCoding+VersionOne.h
│ │ │ │ │ ├── JWTCoding+VersionOne.m
│ │ │ │ │ ├── JWTCoding+VersionThree.h
│ │ │ │ │ ├── JWTCoding+VersionThree.m
│ │ │ │ │ ├── JWTCoding+VersionTwo.h
│ │ │ │ │ ├── JWTCoding+VersionTwo.m
│ │ │ │ │ ├── JWTCoding.h
│ │ │ │ │ └── JWTCoding.m
│ │ │ │ ├── FrameworkSupplement/
│ │ │ │ │ ├── JWT.h
│ │ │ │ │ └── Map.modulemap
│ │ │ │ └── Supplement/
│ │ │ │ ├── JWTBase64Coder.h
│ │ │ │ ├── JWTBase64Coder.m
│ │ │ │ ├── JWTDeprecations.h
│ │ │ │ ├── JWTErrorDescription.h
│ │ │ │ └── JWTErrorDescription.m
│ │ │ ├── LICENSE
│ │ │ └── README.md
│ │ ├── RCTConvert+CodePushInstallMode.m
│ │ ├── RCTConvert+CodePushUpdateState.m
│ │ └── SSZipArchive/
│ │ ├── Info.plist
│ │ ├── README.md
│ │ ├── SSZipArchive.h
│ │ ├── SSZipArchive.m
│ │ ├── SSZipCommon.h
│ │ ├── Supporting Files/
│ │ │ └── PrivacyInfo.xcprivacy
│ │ ├── include/
│ │ │ └── ZipArchive.h
│ │ └── minizip/
│ │ ├── LICENSE
│ │ ├── mz.h
│ │ ├── mz_compat.c
│ │ ├── mz_compat.h
│ │ ├── mz_crypt.c
│ │ ├── mz_crypt.h
│ │ ├── mz_crypt_apple.c
│ │ ├── mz_os.c
│ │ ├── mz_os.h
│ │ ├── mz_os_posix.c
│ │ ├── mz_strm.c
│ │ ├── mz_strm.h
│ │ ├── mz_strm_buf.c
│ │ ├── mz_strm_buf.h
│ │ ├── mz_strm_mem.c
│ │ ├── mz_strm_mem.h
│ │ ├── mz_strm_os.h
│ │ ├── mz_strm_os_posix.c
│ │ ├── mz_strm_pkcrypt.c
│ │ ├── mz_strm_pkcrypt.h
│ │ ├── mz_strm_split.c
│ │ ├── mz_strm_split.h
│ │ ├── mz_strm_wzaes.c
│ │ ├── mz_strm_wzaes.h
│ │ ├── mz_strm_zlib.c
│ │ ├── mz_strm_zlib.h
│ │ ├── mz_zip.c
│ │ ├── mz_zip.h
│ │ ├── mz_zip_rw.c
│ │ └── mz_zip_rw.h
│ ├── CodePush.xcodeproj/
│ │ ├── project.pbxproj
│ │ └── project.xcworkspace/
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata/
│ │ └── IDEWorkspaceChecks.plist
│ └── PrivacyInfo.xcprivacy
├── logging.js
├── package-mixins.js
├── package.json
├── react-native.config.js
├── request-fetch-adapter.js
├── scripts/
│ ├── generateBundledResourcesHash.js
│ ├── getFilesInFolder.js
│ ├── postlink/
│ │ ├── android/
│ │ │ └── postlink.js
│ │ ├── ios/
│ │ │ └── postlink.js
│ │ └── run.js
│ ├── postunlink/
│ │ ├── android/
│ │ │ └── postunlink.js
│ │ ├── ios/
│ │ │ └── postunlink.js
│ │ └── run.js
│ ├── recordFilesBeforeBundleCommand.js
│ └── tools/
│ ├── linkToolsAndroid.js
│ └── linkToolsIos.js
├── test/
│ ├── template/
│ │ ├── android/
│ │ │ └── app/
│ │ │ └── src/
│ │ │ └── main/
│ │ │ ├── java/
│ │ │ │ └── com/
│ │ │ │ └── testcodepush/
│ │ │ │ └── MainApplication.java
│ │ │ └── res/
│ │ │ └── values/
│ │ │ └── strings.xml
│ │ ├── codePushWrapper.js
│ │ ├── index.js
│ │ ├── ios/
│ │ │ └── TestCodePush/
│ │ │ └── AppDelegate.mm
│ │ └── scenarios/
│ │ ├── scenarioCheckForUpdate.js
│ │ ├── scenarioCheckForUpdateCustomKey.js
│ │ ├── scenarioDisallowRestartImmediate.js
│ │ ├── scenarioDisallowRestartOnResume.js
│ │ ├── scenarioDisallowRestartOnSuspend.js
│ │ ├── scenarioDownloadUpdate.js
│ │ ├── scenarioInstall.js
│ │ ├── scenarioInstallOnRestartWithRevert.js
│ │ ├── scenarioInstallOnResumeWithRevert.js
│ │ ├── scenarioInstallOnSuspendWithRevert.js
│ │ ├── scenarioInstallRestart2x.js
│ │ ├── scenarioInstallWithRevert.js
│ │ ├── scenarioRestart.js
│ │ ├── scenarioRestart2x.js
│ │ ├── scenarioSync.js
│ │ ├── scenarioSync2x.js
│ │ ├── scenarioSyncMandatoryDefault.js
│ │ ├── scenarioSyncMandatoryRestart.js
│ │ ├── scenarioSyncMandatoryResume.js
│ │ ├── scenarioSyncMandatorySuspend.js
│ │ ├── scenarioSyncRestartDelay.js
│ │ ├── scenarioSyncResume.js
│ │ ├── scenarioSyncResumeDelay.js
│ │ ├── scenarioSyncSuspend.js
│ │ ├── scenarioSyncSuspendDelay.js
│ │ ├── updateDeviceReady.js
│ │ ├── updateNARConditional.js
│ │ ├── updateNotifyApplicationReady.js
│ │ ├── updateSync.js
│ │ └── updateSync2x.js
│ └── test.ts
├── tsconfig.json
├── tslint.json
├── typings/
│ └── react-native-code-push.d.ts
├── windows/
│ ├── .gitignore
│ ├── .npmignore
│ ├── CodePush/
│ │ ├── .npmignore
│ │ ├── CodePush.def
│ │ ├── CodePush.vcxproj
│ │ ├── CodePush.vcxproj.filters
│ │ ├── CodePushConfig.cpp
│ │ ├── CodePushConfig.h
│ │ ├── CodePushConfig.idl
│ │ ├── CodePushDownloadHandler.cpp
│ │ ├── CodePushDownloadHandler.h
│ │ ├── CodePushNativeModule.cpp
│ │ ├── CodePushNativeModule.h
│ │ ├── CodePushPackage.cpp
│ │ ├── CodePushPackage.h
│ │ ├── CodePushTelemetryManager.cpp
│ │ ├── CodePushTelemetryManager.h
│ │ ├── CodePushUpdateUtils.cpp
│ │ ├── CodePushUpdateUtils.h
│ │ ├── CodePushUtils.cpp
│ │ ├── CodePushUtils.h
│ │ ├── FileUtils.cpp
│ │ ├── FileUtils.h
│ │ ├── PropertySheet.props
│ │ ├── ReactPackageProvider.cpp
│ │ ├── ReactPackageProvider.h
│ │ ├── ReactPackageProvider.idl
│ │ ├── miniz/
│ │ │ ├── LICENSE
│ │ │ ├── miniz.c
│ │ │ ├── miniz.h
│ │ │ └── readme.md
│ │ ├── packages.config
│ │ ├── pch.cpp
│ │ └── pch.h
│ └── Directory.Build.props
└── windows-legacy/
├── .gitignore
├── .npmignore
├── CodePush/
│ ├── .gitignore
│ ├── CodePush.csproj
│ ├── CodePushUtils.cs
│ ├── FileUtils.cs
│ ├── Properties/
│ │ ├── AssemblyInfo.cs
│ │ └── CodePush.rd.xml
│ ├── UpdateManager.cs
│ └── UpdateUtils.cs
├── CodePush.Net46/
│ ├── .gitignore
│ ├── Adapters/
│ │ ├── Http/
│ │ │ └── HttpProgress.cs
│ │ └── Storage/
│ │ └── ApplicationDataContainer.cs
│ ├── CodePush.Net46.csproj
│ ├── CodePushUtils.cs
│ ├── FileUtils.cs
│ ├── Properties/
│ │ └── AssemblyInfo.cs
│ ├── UpdateManager.cs
│ ├── UpdateUtils.cs
│ └── packages.config
├── CodePush.Net46.Test/
│ ├── ApplicationDataContainerTest.cs
│ ├── CodePush.Net46.Test.csproj
│ ├── Properties/
│ │ └── AssemblyInfo.cs
│ ├── TelemetryManagerTest.cs
│ ├── app.config
│ └── packages.config
└── CodePush.Shared/
├── CodePush.Shared.projitems
├── CodePush.Shared.shproj
├── CodePushConstants.cs
├── CodePushNativeModule.cs
├── CodePushReactPackage.cs
├── CodePushUtils.cs
├── InstallMode.cs
├── MinimumBackgroundListener.cs
├── SettingsManager.cs
├── TelemetryManager.cs
└── UpdateState.cs
================================================
FILE CONTENTS
================================================
================================================
FILE: .azurepipelines/build-rn-code-push-1es.yml
================================================
trigger:
- master
pr:
- master
resources:
repositories:
- repository: 1ESPipelineTemplates
type: git
name: 1ESPipelineTemplates/1ESPipelineTemplates
ref: refs/tags/release
name: $(Build.SourceBranchName)_$(date:yyyyMMdd)$(rev:.r)
extends:
${{ if eq(variables['Build.SourceBranch'], 'refs/heads/master') }}:
template: v1/1ES.Official.PipelineTemplate.yml@1ESPipelineTemplates
${{ else }}:
template: v1/1ES.Unofficial.PipelineTemplate.yml@1ESPipelineTemplates
parameters:
pool:
name: 1ES-PT-CBL-Mariner-2.0-Gen2
os: linux
customBuildTags:
- ES365AIMigrationTooling-BulkMigrated
sdl:
sourceAnalysisPool: 1ES-PT-Windows-2022
credscan:
suppressionsFile: $(Build.SourcesDirectory)/.config/CredScanSuppressions.json
stages:
- stage: Stage
jobs:
- job: HostJob
templateContext:
outputs:
- output: pipelineArtifact
displayName: "Publish Artifact: artifacts"
path: '$(Build.ArtifactStagingDirectory)/npm'
artifactName: npm
steps:
- task: NodeTool@0
inputs:
versionSpec: '14.x'
displayName: 'Install Node.js'
- script: |
npm pack
npm install -g react-native-code-push*.tgz
displayName: 'Package react-native-code-push'
workingDirectory: $(Build.SourcesDirectory)
- task: DeleteFiles@1
inputs:
contents: node_modules
displayName: 'Delete node_modules'
- task: ArchiveFiles@2
inputs:
rootFolderOrFile: '$(Build.SourcesDirectory)'
includeRootFolder: false
archiveType: 'tar'
archiveFile: '$(Build.ArtifactStagingDirectory)/npm/$(Build.BuildId).tgz'
replaceExistingArchive: true
verbose: true
displayName: 'Prepare npm artifact'
- stage: APIScan
dependsOn: Stage
pool:
name: 1ES-PT-Windows-2022
os: windows
variables:
"agent.source.skip": true
jobs:
- job: APIScan
steps:
- task: DownloadPipelineArtifact@2
displayName: Download Build Artifacts for APIScan
inputs:
artifactName: npm
targetPath: '$(Agent.BuildDirectory)/npm'
- task: ExtractFiles@1
inputs:
archiveFilePatterns: '$(Agent.BuildDirectory)/npm/*.tgz'
destinationFolder: '$(Agent.BuildDirectory)/npm_extracted'
- task: AzureKeyVault@2
inputs:
azureSubscription: 'AC - Dev Infra & Build Pool'
KeyVaultName: 'mobile-center-sdk'
SecretsFilter: 'appcenter-sdk-managed-identity-clientid'
RunAsPreJob: false
- task: APIScan@2
displayName: 'Run APIScan'
inputs:
softwareFolder: '$(Agent.BuildDirectory)\npm_extracted'
softwareName: 'react-native-code-push'
softwareVersionNum: '$(Build.BuildId)'
isLargeApp: false
toolVersion: 'Latest'
verbosityLevel: verbose
condition: and(succeeded(), ne(variables['DisableAPIScan'], 'true'))
env:
AzureServicesAuthConnectionString: 'runAs=App;AppId=$(appcenter-sdk-managed-identity-clientid)'
================================================
FILE: .azurepipelines/test-rn-code-push.yml
================================================
trigger:
- master
pr:
- master
variables:
- name: api-level
value: '27'
pool:
vmImage: 'macOS-12'
stages:
- stage: RunTests
displayName: 'Run Android & IOS tests'
jobs:
- job: TestAndroid
timeoutInMinutes: 120
displayName: 'Test android'
steps:
- script: |
adb devices
displayName: 'Start adb server'
- script: |
$ANDROID_HOME/tools/bin/sdkmanager "system-images;android-$(api-level);google_apis;x86"
displayName: 'Download system image'
- script: |
$ANDROID_HOME/tools/bin/avdmanager create avd --force --name TestEmulator --abi google_apis/x86 --package 'system-images;android-$(api-level);google_apis;x86' --device "Nexus 6P"
displayName: 'Creating Android emulator'
- script: |
$ANDROID_HOME/emulator/emulator -avd TestEmulator -noaudio -no-window -no-snapshot-save -no-boot-anim -memory 6144 &
displayName: 'Start Android emulator'
- script: |
$ANDROID_HOME/platform-tools/adb wait-for-device shell 'while [[ -z $(getprop sys.boot_completed | tr -d '\r') ]]; do sleep 1; done'
displayName: 'Wait for emulator to boot'
- script: |
adb shell settings put global window_animation_scale 0.0
displayName: 'Disable animations and transitions'
- script: |
adb shell settings put global transition_animation_scale 0.0
displayName: 'Disable animations and transitions'
- script: |
adb shell settings put global animator_duration_scale 0.0
displayName: 'Disable animations and transitions'
- task: JavaToolInstaller@0
inputs:
versionSpec: '11'
jdkArchitectureOption: 'x64'
jdkSourceOption: 'PreInstalled'
displayName: 'Change Java version'
- script: |
npm install
displayName: 'Package Installation'
- script: |
npm run build:tests && npm run test:setup:android
displayName: 'Setup Android tests'
- script: |
npm run test:fast:android
displayName: 'Run Android test'
- job: TestIOS
timeoutInMinutes: 120
displayName: 'Test IOS'
steps:
- script: |
npm install
displayName: 'Install dependencies'
- script: |
npm run build:tests && npm run test:setup:ios
displayName: 'Setup iOS tests'
- script: |
npm run test:fast:ios
displayName: 'Run tests'
================================================
FILE: .config/CredScanSuppressions.json
================================================
{
"tool": "Credential Scanner",
"suppressions": [
{
"file": "/Examples/CodePushDemoApp/android/app/debug.keystore",
"_justification": "Used only in DemoApp"
},
{
"file": "/Examples/CodePushDemoAppCpp/windows/CodePushDemoAppCpp/CodePushDemoAppCpp_TemporaryKey.pfx",
"_justification": "Used only in DemoApp"
}
]
}
================================================
FILE: .github/CODEOWNERS
================================================
* @microsoft/appcenter-fte
================================================
FILE: .github/ISSUE_TEMPLATE.md
================================================
Thanks so much for filing an issue or feature request! Please fill out the following (wherever relevant):
### Steps to Reproduce
1.
2.
3.
### Expected Behavior
What you expected to happen?
### Actual Behavior
What actually happens?
```
STACK TRACE AND/OR SCREENSHOTS
```
### Reproducible Demo
* Download https://github.com/microsoft/react-native-code-push/archive/master.zip and unzip. From `Examples` folder run `node create-app.js appName react-native@0.71.19 react-native-code-push@9.0.1` command to generate plain CodePushified React Native app. Please see description on top of `create-app.js` file content if needed
* If you can't reproduce the bug on it, provide us as much info as possible about your project
### Environment
* react-native-code-push version:
* react-native version:
* iOS/Android/Windows version:
* Does this reproduce on a debug build or release build?
* Does this reproduce on a simulator, or only on a physical device?
(The more info the faster we will be able to address it!)
================================================
FILE: .github/policies/resourceManagement.yml
================================================
id:
name: GitOps.PullRequestIssueManagement
description: GitOps.PullRequestIssueManagement primitive
owner:
resource: repository
disabled: false
where:
configuration:
resourceManagementConfiguration:
scheduledSearches:
- description:
frequencies:
- hourly:
hour: 4
filters:
- isOpen
- isNotLabeledWith:
label: bug
- isNotLabeledWith:
label: security
- isNotLabeledWith:
label: stale
- isNotLabeledWith:
label: do not close
- noActivitySince:
days: 60
- isIssue
- isNotAssigned
actions:
- addLabel:
label: stale
- addReply:
reply: This issue has been automatically marked as stale because it has not had any activity for 60 days. It will be closed if no further activity occurs within 15 days of this comment.
- description:
frequencies:
- hourly:
hour: 6
filters:
- isOpen
- isIssue
- hasLabel:
label: stale
- isNotLabeledWith:
label: bug
- isNotLabeledWith:
label: do not close
- isNotAssigned
- noActivitySince:
days: 15
actions:
- addReply:
reply: This issue will now be closed because it hasn't had any activity for 15 days after stale. Please feel free to open a new issue if you still have a question/issue or suggestion.
- closeIssue
eventResponderTasks:
- if:
- payloadType: Issue_Comment
- hasLabel:
label: stale
then:
- removeLabel:
label: stale
description:
onFailure:
onSuccess:
================================================
FILE: .gitignore
================================================
# gitignore contributors: remember to update .npmignore
# OSX
#
.DS_Store
# Xcode
#
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
## Build generated
build/
DerivedData
## Various settings
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata
## Other
*.xccheckout
*.moved-aside
*.xcuserstate
## Obj-C/Swift specific
*.hmap
*.ipa
# CocoaPods
#
# We recommend against adding the Pods directory to your .gitignore. However
# you should judge for yourself, the pros and cons are mentioned at:
# http://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
#
#Pods/
# Carthage
#
# Add this line if you want to avoid checking in source code from Carthage dependencies.
# Carthage/Checkouts
Carthage/Build
### Node
# Logs
logs
*.log
# Runtime data
pids
*.pid
*.seed
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# node-waf configuration
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directory
# https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git
node_modules
# Xcode
#
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
## Build generated
build/
DerivedData
## Various settings
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata
## OSX
.DS_Store
## Other
*.xccheckout
*.moved-aside
*.xcuserstate
# Intellij project files
*.iml
*.ipr
*.iws
.idea/
#Gradle
.gradletasknamecache
.gradle/
build/
bin/
# Built application files
*.apk
*.ap_
# Files for the Dalvik VM
*.dex
# Java class files
*.class
# Generated files
bin/
gen/
# Gradle files
.gradle/
build/
*/build/
# Local configuration file (sdk path, etc)
local.properties
# Proguard folder generated by Eclipse
proguard/
# Log Files
*.log
# Android Studio Navigation editor temp files
.navigation/
# Android Studio captures folder
captures/
# Remove after this framework is published on NPM
code-push-plugin-testing-framework/node_modules
# Windows
windows/.vs/
windows/obj/
#Visual Studio files
*.[Oo]bj
*.user
*.aps
*.pch
*.vspscc
*.vssscc
*_i.c
*_p.c
*.ncb
*.suo
*.tlb
*.tlh
*.bak
*.[Cc]ache
*.ilk
*.log
*.lib
*.sbr
*.sdf
*.opensdf
*.opendb
*.unsuccessfulbuild
ipch/
[Oo]bj/
[Bb]in
[Dd]ebug*/
[Rr]elease*/
Ankh.NoLoad
# RN New Version App Generation
Examples/testapp_rn
# Android debug build files (conflict ignoring #Visual Studio files)
!android/app/src/debug/
================================================
FILE: .npmignore
================================================
# OSX
#
.DS_Store
# Xcode
#
build/
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata
*.xccheckout
*.moved-aside
DerivedData
*.hmap
*.ipa
*.xcuserstate
# node.js
#
node_modules/
npm-debug.log
# Don't publish example apps
Examples/
Recipes/
# Don't publish testing code
bin/
test/
# Remove after this framework is published on NPM
code-push-plugin-testing-framework/
# Android build artifacts and Android Studio bits
android/app/build
android/local.properties
android/.gradle
android/**/*.iml
android/.idea
# Windows
windows/.vs/
windows/obj/
#Tests
windows/CodePush.Net46.Test
#Visual Studio files
*.[Oo]bj
*.user
*.aps
*.pch
*.vspscc
*.vssscc
*_i.c
*_p.c
*.ncb
*.suo
*.tlb
*.tlh
*.bak
*.[Cc]ache
*.ilk
*.log
*.lib
*.sbr
*.sdf
*.opensdf
*.opendb
*.unsuccessfulbuild
ipch/
[Oo]bj/
[Bb]in
[Dd]ebug*/
[Rr]elease*/
Ankh.NoLoad
#NuGet
packages/
*.nupkg
# VSCode
.vscode/
# IntelliJIDEA
.idea/
# Github
.github/
# Git
.git/
.watchmanconfig
================================================
FILE: .vscode/launch.json
================================================
{
"version": "0.2.0",
"configurations": [
{
"name": "Tests-android",
"type": "node",
"request": "launch",
"preLaunchTask": "Setup-android",
"runtimeExecutable": "npm",
"runtimeArgs": [
"run",
"test:debugger:android"
],
"port": 9229,
"stopOnEntry": false,
"sourceMaps": true,
"console": "internalConsole",
"internalConsoleOptions": "openOnSessionStart",
"autoAttachChildProcesses": true,
"timeout": 100000
},
{
"name": "Tests-ios",
"type": "node",
"request": "launch",
"preLaunchTask": "Setup-ios",
"runtimeExecutable": "npm",
"runtimeArgs": [
"run",
"test:debugger:ios"
],
"port": 9229,
"stopOnEntry": false,
"sourceMaps": true,
"console": "internalConsole",
"internalConsoleOptions": "openOnSessionStart",
"autoAttachChildProcesses": true,
"timeout": 100000
}
]
}
================================================
FILE: .vscode/settings.json
================================================
{
"typescript.tsdk": "node_modules/typescript/lib"
}
================================================
FILE: .vscode/tasks.json
================================================
{
"version": "2.0.0",
"tasks": [
{
"type": "shell",
"label": "Build",
"command": "npm",
"args": [
"run",
"build:tests"
],
"presentation": {
"echo": false,
"focus": false
},
"problemMatcher": [
"$tsc"
]
},
{
"type": "shell",
"label": "Setup-android",
"dependsOn": "Build",
"command": "npm",
"args": [
"run",
"test:setup:android"
],
"presentation": {
"echo": false,
"focus": false
},
"problemMatcher": [
"$tsc"
]
},
{
"type": "shell",
"label": "Setup-ios",
"dependsOn": "Build",
"command": "npm",
"args": [
"run",
"test:setup:ios"
],
"presentation": {
"echo": false,
"focus": false
},
"problemMatcher": [
"$tsc"
]
}
]
}
================================================
FILE: .watchmanconfig
================================================
{}
================================================
FILE: AlertAdapter.js
================================================
import React, { Platform } from "react-native";
let { Alert } = React;
if (Platform.OS === "android") {
const { NativeModules: { CodePushDialog } } = React;
Alert = {
alert(title, message, buttons) {
if (buttons.length > 2) {
throw "Can only show 2 buttons for Android dialog.";
}
const button1Text = buttons[0] ? buttons[0].text : null,
button2Text = buttons[1] ? buttons[1].text : null;
CodePushDialog.showDialog(
title, message, button1Text, button2Text,
(buttonId) => { buttons[buttonId].onPress && buttons[buttonId].onPress(); },
(error) => { throw error; });
}
};
}
module.exports = { Alert };
================================================
FILE: CONTRIBUTING.md
================================================
# Contributing
## Using the plugin
### Environment setup
`node.js` and `npm` are needed for using this project. `npm` comes bundled with the `node.js` installer. You can download the `node.js` installer here: https://nodejs.org/download/.
Once you have installed `node.js` and `npm`, install the dev dependencies for the project.
```
npm install
```
### Using the plugin manually
Follow these steps to test your modifications to the plugin manually:
- clone this repository
- install the dependencies
Navigate to the root folder from your command line console and run:
```
npm install
```
- install the plugin in a React-Native project
Navigate to the root folder of your React-Native project from your command line console and run:
```
npm install local_path_to_your_clone_of_this_repo
```
- configure the plugin using the steps in the README.md
- build and run your app on an emulator or device
## Test
### Environment setup
First, make sure you have installed the dependencies for the plugin by following the steps above.
Then, make sure you have installed `react-native`.
```
npm install -g react-native
```
To run Android tests, make sure you have `sdk\tools`, `sdk\emulator` and `sdk\platform-tools` in your PATH.
To run iOS tests, make sure you've installed CocoaPods and have `.gem/bin` in your PATH.
### Supported platforms
The plugin has end to end tests for Android and iOS. Depending on your development machine OS, you can run some or all the tests.
OS | Supported tests
------------- | -------------
OS X | Android, iOS
Windows | Android
### Test descriptions
The tests first build the app.
They then check if the required emulators are currently running.
If an Android emulator is not running, it attempts to boot the latest Android emulator. You can specify an emulator by adding env variable `ANDROID_EMU=yourEmulatorNameHere` to the npm command. For example: `ANDROID_EMU=yourEmulatorNameHere npm run test:android`.
If an iOS simulator is not running, it attempts to boot the latest iOS iPhone simulator. You can specify a simulator by adding env variable `IOS_EMU=yourSimulatorNameHere` to the npm command. For example: `IOS_EMU="iPhone 8 (0567DFF8-329E-41A3-BD6D-E48E9DD5EF39)" npm run test:ios`.
If all the required emulators are not running and the tests fail to boot them, the tests will fail.
If you would like the tests to always restart the necessary emulators (killing them if they are currently running), setup a env variable `CLEAN=true` to the command. For example: `CLEAN=true npm run test`.
The desired unit tests are then run.
If you would like to skip building, add a `:fast` in the command you'd like to run. For example, `npm run test:ios` becomes `npm run test:fast:ios` or `npm run test:android` becomes `npm run test:fast:android`.
There is a both a full unit test suite and a "core" set of unit tests that you may run. If you would like to run only the core tests, setup a env variable `CORE=true` to the command. For example: `CORE=true npm run test:android`.
If you would like to pull the plugin from NPM rather than running the tests on the local version, setup a env variable `NPM=true` to the command. For example: `NPM=true npm run test:ios`.
#### Default
To run all of the unit tests on Android and iOS:
```
npm run test
```
#### iOS
To run all of the unit tests on iOS:
```
npm run test:ios
```
#### Android
To run all of the unit tests on Android:
```
npm run test:android
```
#### More examples
All possible testing configurations have tasks!
The platforms are ordered as follows, and ran in that order:
android, ios
To run the core unit tests on Android:
```
CORE=true npm run test:android
```
To run all of the unit tests on iOS and pull the plugin from NPM:
```
NPM=true npm run test:ios
```
To run all of the unit tests on Android and iOS without building first:
```
npm run test:fast
```
To run all of the unit tests on iOS and restart the emulators:
```
CLEAN=true npm run test:ios
```
To run the core unit tests on Android and pull the plugin from NPM:
```
NPM=true CORE=true npm run test:android
```
...and so on!
================================================
FILE: CodePush.js
================================================
import { AcquisitionManager as Sdk } from "code-push/script/acquisition-sdk";
import { Alert } from "./AlertAdapter";
import requestFetchAdapter from "./request-fetch-adapter";
import { AppState, Platform } from "react-native";
import log from "./logging";
import hoistStatics from 'hoist-non-react-statics';
let NativeCodePush = require("react-native").NativeModules.CodePush;
const PackageMixins = require("./package-mixins")(NativeCodePush);
async function checkForUpdate(deploymentKey = null, handleBinaryVersionMismatchCallback = null) {
/*
* Before we ask the server if an update exists, we
* need to retrieve three pieces of information from the
* native side: deployment key, app version (e.g. 1.0.1)
* and the hash of the currently running update (if there is one).
* This allows the client to only receive updates which are targetted
* for their specific deployment and version and which are actually
* different from the CodePush update they have already installed.
*/
const nativeConfig = await getConfiguration();
/*
* If a deployment key was explicitly provided,
* then let's override the one we retrieved
* from the native-side of the app. This allows
* dynamically "redirecting" end-users at different
* deployments (e.g. an early access deployment for insiders).
*/
const config = deploymentKey ? { ...nativeConfig, ...{ deploymentKey } } : nativeConfig;
const sdk = getPromisifiedSdk(requestFetchAdapter, config);
// Use dynamically overridden getCurrentPackage() during tests.
const localPackage = await module.exports.getCurrentPackage();
/*
* If the app has a previously installed update, and that update
* was targetted at the same app version that is currently running,
* then we want to use its package hash to determine whether a new
* release has been made on the server. Otherwise, we only need
* to send the app version to the server, since we are interested
* in any updates for current binary version, regardless of hash.
*/
let queryPackage;
if (localPackage) {
queryPackage = localPackage;
} else {
queryPackage = { appVersion: config.appVersion };
if (Platform.OS === "ios" && config.packageHash) {
queryPackage.packageHash = config.packageHash;
}
}
const update = await sdk.queryUpdateWithCurrentPackage(queryPackage);
/*
* There are four cases where checkForUpdate will resolve to null:
* ----------------------------------------------------------------
* 1) The server said there isn't an update. This is the most common case.
* 2) The server said there is an update but it requires a newer binary version.
* This would occur when end-users are running an older binary version than
* is available, and CodePush is making sure they don't get an update that
* potentially wouldn't be compatible with what they are running.
* 3) The server said there is an update, but the update's hash is the same as
* the currently running update. This should _never_ happen, unless there is a
* bug in the server, but we're adding this check just to double-check that the
* client app is resilient to a potential issue with the update check.
* 4) The server said there is an update, but the update's hash is the same as that
* of the binary's currently running version. This should only happen in Android -
* unlike iOS, we don't attach the binary's hash to the updateCheck request
* because we want to avoid having to install diff updates against the binary's
* version, which we can't do yet on Android.
*/
if (!update || update.updateAppVersion ||
localPackage && (update.packageHash === localPackage.packageHash) ||
(!localPackage || localPackage._isDebugOnly) && config.packageHash === update.packageHash) {
if (update && update.updateAppVersion) {
log("An update is available but it is not targeting the binary version of your app.");
if (handleBinaryVersionMismatchCallback && typeof handleBinaryVersionMismatchCallback === "function") {
handleBinaryVersionMismatchCallback(update)
}
}
return null;
} else {
const remotePackage = { ...update, ...PackageMixins.remote(sdk.reportStatusDownload) };
remotePackage.failedInstall = await NativeCodePush.isFailedUpdate(remotePackage.packageHash);
remotePackage.deploymentKey = deploymentKey || nativeConfig.deploymentKey;
return remotePackage;
}
}
const getConfiguration = (() => {
let config;
return async function getConfiguration() {
if (config) {
return config;
} else if (testConfig) {
return testConfig;
} else {
config = await NativeCodePush.getConfiguration();
return config;
}
}
})();
async function getCurrentPackage() {
return await getUpdateMetadata(CodePush.UpdateState.LATEST);
}
async function getUpdateMetadata(updateState) {
let updateMetadata = await NativeCodePush.getUpdateMetadata(updateState || CodePush.UpdateState.RUNNING);
if (updateMetadata) {
updateMetadata = {...PackageMixins.local, ...updateMetadata};
updateMetadata.failedInstall = await NativeCodePush.isFailedUpdate(updateMetadata.packageHash);
updateMetadata.isFirstRun = await NativeCodePush.isFirstRun(updateMetadata.packageHash);
}
return updateMetadata;
}
function getPromisifiedSdk(requestFetchAdapter, config) {
// Use dynamically overridden AcquisitionSdk during tests.
const sdk = new module.exports.AcquisitionSdk(requestFetchAdapter, config);
sdk.queryUpdateWithCurrentPackage = (queryPackage) => {
return new Promise((resolve, reject) => {
module.exports.AcquisitionSdk.prototype.queryUpdateWithCurrentPackage.call(sdk, queryPackage, (err, update) => {
if (err) {
reject(err);
} else {
resolve(update);
}
});
});
};
sdk.reportStatusDeploy = (deployedPackage, status, previousLabelOrAppVersion, previousDeploymentKey) => {
return new Promise((resolve, reject) => {
module.exports.AcquisitionSdk.prototype.reportStatusDeploy.call(sdk, deployedPackage, status, previousLabelOrAppVersion, previousDeploymentKey, (err) => {
if (err) {
reject(err);
} else {
resolve();
}
});
});
};
sdk.reportStatusDownload = (downloadedPackage) => {
return new Promise((resolve, reject) => {
module.exports.AcquisitionSdk.prototype.reportStatusDownload.call(sdk, downloadedPackage, (err) => {
if (err) {
reject(err);
} else {
resolve();
}
});
});
};
return sdk;
}
// This ensures that notifyApplicationReadyInternal is only called once
// in the lifetime of this module instance.
const notifyApplicationReady = (() => {
let notifyApplicationReadyPromise;
return () => {
if (!notifyApplicationReadyPromise) {
notifyApplicationReadyPromise = notifyApplicationReadyInternal();
}
return notifyApplicationReadyPromise;
};
})();
async function notifyApplicationReadyInternal() {
await NativeCodePush.notifyApplicationReady();
const statusReport = await NativeCodePush.getNewStatusReport();
statusReport && tryReportStatus(statusReport); // Don't wait for this to complete.
return statusReport;
}
async function tryReportStatus(statusReport, retryOnAppResume) {
const config = await getConfiguration();
const previousLabelOrAppVersion = statusReport.previousLabelOrAppVersion;
const previousDeploymentKey = statusReport.previousDeploymentKey || config.deploymentKey;
try {
if (statusReport.appVersion) {
log(`Reporting binary update (${statusReport.appVersion})`);
if (!config.deploymentKey) {
throw new Error("Deployment key is missed");
}
const sdk = getPromisifiedSdk(requestFetchAdapter, config);
await sdk.reportStatusDeploy(/* deployedPackage */ null, /* status */ null, previousLabelOrAppVersion, previousDeploymentKey);
} else {
const label = statusReport.package.label;
if (statusReport.status === "DeploymentSucceeded") {
log(`Reporting CodePush update success (${label})`);
} else {
log(`Reporting CodePush update rollback (${label})`);
await NativeCodePush.setLatestRollbackInfo(statusReport.package.packageHash);
}
config.deploymentKey = statusReport.package.deploymentKey;
const sdk = getPromisifiedSdk(requestFetchAdapter, config);
await sdk.reportStatusDeploy(statusReport.package, statusReport.status, previousLabelOrAppVersion, previousDeploymentKey);
}
NativeCodePush.recordStatusReported(statusReport);
retryOnAppResume && retryOnAppResume.remove();
} catch (e) {
log(`Report status failed: ${JSON.stringify(statusReport)}`);
NativeCodePush.saveStatusReportForRetry(statusReport);
// Try again when the app resumes
if (!retryOnAppResume) {
const resumeListener = AppState.addEventListener("change", async (newState) => {
if (newState !== "active") return;
const refreshedStatusReport = await NativeCodePush.getNewStatusReport();
if (refreshedStatusReport) {
tryReportStatus(refreshedStatusReport, resumeListener);
} else {
resumeListener && resumeListener.remove();
}
});
}
}
}
async function shouldUpdateBeIgnored(remotePackage, syncOptions) {
let { rollbackRetryOptions } = syncOptions;
const isFailedPackage = remotePackage && remotePackage.failedInstall;
if (!isFailedPackage || !syncOptions.ignoreFailedUpdates) {
return false;
}
if (!rollbackRetryOptions) {
return true;
}
if (typeof rollbackRetryOptions !== "object") {
rollbackRetryOptions = CodePush.DEFAULT_ROLLBACK_RETRY_OPTIONS;
} else {
rollbackRetryOptions = { ...CodePush.DEFAULT_ROLLBACK_RETRY_OPTIONS, ...rollbackRetryOptions };
}
if (!validateRollbackRetryOptions(rollbackRetryOptions)) {
return true;
}
const latestRollbackInfo = await NativeCodePush.getLatestRollbackInfo();
if (!validateLatestRollbackInfo(latestRollbackInfo, remotePackage.packageHash)) {
log("The latest rollback info is not valid.");
return true;
}
const { delayInHours, maxRetryAttempts } = rollbackRetryOptions;
const hoursSinceLatestRollback = (Date.now() - latestRollbackInfo.time) / (1000 * 60 * 60);
if (hoursSinceLatestRollback >= delayInHours && maxRetryAttempts >= latestRollbackInfo.count) {
log("Previous rollback should be ignored due to rollback retry options.");
return false;
}
return true;
}
function validateLatestRollbackInfo(latestRollbackInfo, packageHash) {
return latestRollbackInfo &&
latestRollbackInfo.time &&
latestRollbackInfo.count &&
latestRollbackInfo.packageHash &&
latestRollbackInfo.packageHash === packageHash;
}
function validateRollbackRetryOptions(rollbackRetryOptions) {
if (typeof rollbackRetryOptions.delayInHours !== "number") {
log("The 'delayInHours' rollback retry parameter must be a number.");
return false;
}
if (typeof rollbackRetryOptions.maxRetryAttempts !== "number") {
log("The 'maxRetryAttempts' rollback retry parameter must be a number.");
return false;
}
if (rollbackRetryOptions.maxRetryAttempts < 1) {
log("The 'maxRetryAttempts' rollback retry parameter cannot be less then 1.");
return false;
}
return true;
}
var testConfig;
// This function is only used for tests. Replaces the default SDK, configuration and native bridge
function setUpTestDependencies(testSdk, providedTestConfig, testNativeBridge) {
if (testSdk) module.exports.AcquisitionSdk = testSdk;
if (providedTestConfig) testConfig = providedTestConfig;
if (testNativeBridge) NativeCodePush = testNativeBridge;
}
async function restartApp(onlyIfUpdateIsPending = false) {
NativeCodePush.restartApp(onlyIfUpdateIsPending);
}
// This function allows only one syncInternal operation to proceed at any given time.
// Parallel calls to sync() while one is ongoing yields CodePush.SyncStatus.SYNC_IN_PROGRESS.
const sync = (() => {
let syncInProgress = false;
const setSyncCompleted = () => { syncInProgress = false; };
return (options = {}, syncStatusChangeCallback, downloadProgressCallback, handleBinaryVersionMismatchCallback) => {
let syncStatusCallbackWithTryCatch, downloadProgressCallbackWithTryCatch;
if (typeof syncStatusChangeCallback === "function") {
syncStatusCallbackWithTryCatch = (...args) => {
try {
syncStatusChangeCallback(...args);
} catch (error) {
log(`An error has occurred : ${error.stack}`);
}
}
}
if (typeof downloadProgressCallback === "function") {
downloadProgressCallbackWithTryCatch = (...args) => {
try {
downloadProgressCallback(...args);
} catch (error) {
log(`An error has occurred: ${error.stack}`);
}
}
}
if (syncInProgress) {
typeof syncStatusCallbackWithTryCatch === "function"
? syncStatusCallbackWithTryCatch(CodePush.SyncStatus.SYNC_IN_PROGRESS)
: log("Sync already in progress.");
return Promise.resolve(CodePush.SyncStatus.SYNC_IN_PROGRESS);
}
syncInProgress = true;
const syncPromise = syncInternal(options, syncStatusCallbackWithTryCatch, downloadProgressCallbackWithTryCatch, handleBinaryVersionMismatchCallback);
syncPromise
.then(setSyncCompleted)
.catch(setSyncCompleted);
return syncPromise;
};
})();
/*
* The syncInternal method provides a simple, one-line experience for
* incorporating the check, download and installation of an update.
*
* It simply composes the existing API methods together and adds additional
* support for respecting mandatory updates, ignoring previously failed
* releases, and displaying a standard confirmation UI to the end-user
* when an update is available.
*/
async function syncInternal(options = {}, syncStatusChangeCallback, downloadProgressCallback, handleBinaryVersionMismatchCallback) {
let resolvedInstallMode;
const syncOptions = {
deploymentKey: null,
ignoreFailedUpdates: true,
rollbackRetryOptions: null,
installMode: CodePush.InstallMode.ON_NEXT_RESTART,
mandatoryInstallMode: CodePush.InstallMode.IMMEDIATE,
minimumBackgroundDuration: 0,
updateDialog: null,
...options
};
syncStatusChangeCallback = typeof syncStatusChangeCallback === "function"
? syncStatusChangeCallback
: (syncStatus) => {
switch(syncStatus) {
case CodePush.SyncStatus.CHECKING_FOR_UPDATE:
log("Checking for update.");
break;
case CodePush.SyncStatus.AWAITING_USER_ACTION:
log("Awaiting user action.");
break;
case CodePush.SyncStatus.DOWNLOADING_PACKAGE:
log("Downloading package.");
break;
case CodePush.SyncStatus.INSTALLING_UPDATE:
log("Installing update.");
break;
case CodePush.SyncStatus.UP_TO_DATE:
log("App is up to date.");
break;
case CodePush.SyncStatus.UPDATE_IGNORED:
log("User cancelled the update.");
break;
case CodePush.SyncStatus.UPDATE_INSTALLED:
if (resolvedInstallMode == CodePush.InstallMode.ON_NEXT_RESTART) {
log("Update is installed and will be run on the next app restart.");
} else if (resolvedInstallMode == CodePush.InstallMode.ON_NEXT_RESUME) {
if (syncOptions.minimumBackgroundDuration > 0) {
log(`Update is installed and will be run after the app has been in the background for at least ${syncOptions.minimumBackgroundDuration} seconds.`);
} else {
log("Update is installed and will be run when the app next resumes.");
}
}
break;
case CodePush.SyncStatus.UNKNOWN_ERROR:
log("An unknown error occurred.");
break;
}
};
try {
await CodePush.notifyApplicationReady();
syncStatusChangeCallback(CodePush.SyncStatus.CHECKING_FOR_UPDATE);
const remotePackage = await checkForUpdate(syncOptions.deploymentKey, handleBinaryVersionMismatchCallback);
const doDownloadAndInstall = async () => {
syncStatusChangeCallback(CodePush.SyncStatus.DOWNLOADING_PACKAGE);
const localPackage = await remotePackage.download(downloadProgressCallback);
// Determine the correct install mode based on whether the update is mandatory or not.
resolvedInstallMode = localPackage.isMandatory ? syncOptions.mandatoryInstallMode : syncOptions.installMode;
syncStatusChangeCallback(CodePush.SyncStatus.INSTALLING_UPDATE);
await localPackage.install(resolvedInstallMode, syncOptions.minimumBackgroundDuration, () => {
syncStatusChangeCallback(CodePush.SyncStatus.UPDATE_INSTALLED);
});
return CodePush.SyncStatus.UPDATE_INSTALLED;
};
const updateShouldBeIgnored = await shouldUpdateBeIgnored(remotePackage, syncOptions);
if (!remotePackage || updateShouldBeIgnored) {
if (updateShouldBeIgnored) {
log("An update is available, but it is being ignored due to having been previously rolled back.");
}
const currentPackage = await CodePush.getCurrentPackage();
if (currentPackage && currentPackage.isPending) {
syncStatusChangeCallback(CodePush.SyncStatus.UPDATE_INSTALLED);
return CodePush.SyncStatus.UPDATE_INSTALLED;
} else {
syncStatusChangeCallback(CodePush.SyncStatus.UP_TO_DATE);
return CodePush.SyncStatus.UP_TO_DATE;
}
} else if (syncOptions.updateDialog) {
// updateDialog supports any truthy value (e.g. true, "goo", 12),
// but we should treat a non-object value as just the default dialog
if (typeof syncOptions.updateDialog !== "object") {
syncOptions.updateDialog = CodePush.DEFAULT_UPDATE_DIALOG;
} else {
syncOptions.updateDialog = { ...CodePush.DEFAULT_UPDATE_DIALOG, ...syncOptions.updateDialog };
}
return await new Promise((resolve, reject) => {
let message = null;
let installButtonText = null;
const dialogButtons = [];
if (remotePackage.isMandatory) {
message = syncOptions.updateDialog.mandatoryUpdateMessage;
installButtonText = syncOptions.updateDialog.mandatoryContinueButtonLabel;
} else {
message = syncOptions.updateDialog.optionalUpdateMessage;
installButtonText = syncOptions.updateDialog.optionalInstallButtonLabel;
// Since this is an optional update, add a button
// to allow the end-user to ignore it
dialogButtons.push({
text: syncOptions.updateDialog.optionalIgnoreButtonLabel,
onPress: () => {
syncStatusChangeCallback(CodePush.SyncStatus.UPDATE_IGNORED);
resolve(CodePush.SyncStatus.UPDATE_IGNORED);
}
});
}
// Since the install button should be placed to the
// right of any other button, add it last
dialogButtons.push({
text: installButtonText,
onPress:() => {
doDownloadAndInstall()
.then(resolve, reject);
}
})
// If the update has a description, and the developer
// explicitly chose to display it, then set that as the message
if (syncOptions.updateDialog.appendReleaseDescription && remotePackage.description) {
message += `${syncOptions.updateDialog.descriptionPrefix} ${remotePackage.description}`;
}
syncStatusChangeCallback(CodePush.SyncStatus.AWAITING_USER_ACTION);
Alert.alert(syncOptions.updateDialog.title, message, dialogButtons);
});
} else {
return await doDownloadAndInstall();
}
} catch (error) {
syncStatusChangeCallback(CodePush.SyncStatus.UNKNOWN_ERROR);
log(error.message);
throw error;
}
};
let CodePush;
function codePushify(options = {}) {
let React;
let ReactNative = require("react-native");
try { React = require("react"); } catch (e) { }
if (!React) {
try { React = ReactNative.React; } catch (e) { }
if (!React) {
throw new Error("Unable to find the 'React' module.");
}
}
if (!React.Component) {
throw new Error(
`Unable to find the "Component" class, please either:
1. Upgrade to a newer version of React Native that supports it, or
2. Call the codePush.sync API in your component instead of using the @codePush decorator`
);
}
const decorator = (RootComponent) => {
class CodePushComponent extends React.Component {
constructor(props) {
super(props);
this.rootComponentRef = React.createRef();
}
componentDidMount() {
if (options.checkFrequency === CodePush.CheckFrequency.MANUAL) {
CodePush.notifyAppReady();
} else {
const rootComponentInstance = this.rootComponentRef.current;
let syncStatusCallback;
if (rootComponentInstance && rootComponentInstance.codePushStatusDidChange) {
syncStatusCallback = rootComponentInstance.codePushStatusDidChange.bind(rootComponentInstance);
}
let downloadProgressCallback;
if (rootComponentInstance && rootComponentInstance.codePushDownloadDidProgress) {
downloadProgressCallback = rootComponentInstance.codePushDownloadDidProgress.bind(rootComponentInstance);
}
let handleBinaryVersionMismatchCallback;
if (rootComponentInstance && rootComponentInstance.codePushOnBinaryVersionMismatch) {
handleBinaryVersionMismatchCallback = rootComponentInstance.codePushOnBinaryVersionMismatch.bind(rootComponentInstance);
}
CodePush.sync(options, syncStatusCallback, downloadProgressCallback, handleBinaryVersionMismatchCallback);
if (options.checkFrequency === CodePush.CheckFrequency.ON_APP_RESUME) {
ReactNative.AppState.addEventListener("change", (newState) => {
if (newState === "active") {
CodePush.sync(options, syncStatusCallback, downloadProgressCallback);
}
});
}
}
}
render() {
const props = {...this.props};
// We can set ref property on class components only (not stateless)
// Check it by render method
if (RootComponent.prototype && RootComponent.prototype.render) {
props.ref = this.rootComponentRef;
}
return
}
}
return hoistStatics(CodePushComponent, RootComponent);
}
if (typeof options === "function") {
// Infer that the root component was directly passed to us.
return decorator(options);
} else {
return decorator;
}
}
// If the "NativeCodePush" variable isn't defined, then
// the app didn't properly install the native module,
// and therefore, it doesn't make sense initializing
// the JS interface when it wouldn't work anyways.
if (NativeCodePush) {
CodePush = codePushify;
Object.assign(CodePush, {
AcquisitionSdk: Sdk,
checkForUpdate,
getConfiguration,
getCurrentPackage,
getUpdateMetadata,
log,
notifyAppReady: notifyApplicationReady,
notifyApplicationReady,
restartApp,
setUpTestDependencies,
sync,
disallowRestart: NativeCodePush.disallow,
allowRestart: NativeCodePush.allow,
clearUpdates: NativeCodePush.clearUpdates,
InstallMode: {
IMMEDIATE: NativeCodePush.codePushInstallModeImmediate, // Restart the app immediately
ON_NEXT_RESTART: NativeCodePush.codePushInstallModeOnNextRestart, // Don't artificially restart the app. Allow the update to be "picked up" on the next app restart
ON_NEXT_RESUME: NativeCodePush.codePushInstallModeOnNextResume, // Restart the app the next time it is resumed from the background
ON_NEXT_SUSPEND: NativeCodePush.codePushInstallModeOnNextSuspend // Restart the app _while_ it is in the background,
// but only after it has been in the background for "minimumBackgroundDuration" seconds (0 by default),
// so that user context isn't lost unless the app suspension is long enough to not matter
},
SyncStatus: {
UP_TO_DATE: 0, // The running app is up-to-date
UPDATE_INSTALLED: 1, // The app had an optional/mandatory update that was successfully downloaded and is about to be installed.
UPDATE_IGNORED: 2, // The app had an optional update and the end-user chose to ignore it
UNKNOWN_ERROR: 3,
SYNC_IN_PROGRESS: 4, // There is an ongoing "sync" operation in progress.
CHECKING_FOR_UPDATE: 5,
AWAITING_USER_ACTION: 6,
DOWNLOADING_PACKAGE: 7,
INSTALLING_UPDATE: 8
},
CheckFrequency: {
ON_APP_START: 0,
ON_APP_RESUME: 1,
MANUAL: 2
},
UpdateState: {
RUNNING: NativeCodePush.codePushUpdateStateRunning,
PENDING: NativeCodePush.codePushUpdateStatePending,
LATEST: NativeCodePush.codePushUpdateStateLatest
},
DeploymentStatus: {
FAILED: "DeploymentFailed",
SUCCEEDED: "DeploymentSucceeded",
},
DEFAULT_UPDATE_DIALOG: {
appendReleaseDescription: false,
descriptionPrefix: " Description: ",
mandatoryContinueButtonLabel: "Continue",
mandatoryUpdateMessage: "An update is available that must be installed.",
optionalIgnoreButtonLabel: "Ignore",
optionalInstallButtonLabel: "Install",
optionalUpdateMessage: "An update is available. Would you like to install it?",
title: "Update available"
},
DEFAULT_ROLLBACK_RETRY_OPTIONS: {
delayInHours: 24,
maxRetryAttempts: 1
}
});
} else {
log("The CodePush module doesn't appear to be properly installed. Please double-check that everything is setup correctly.");
}
module.exports = CodePush;
================================================
FILE: CodePush.podspec
================================================
require 'json'
package = JSON.parse(File.read(File.join(__dir__, 'package.json')))
Pod::Spec.new do |s|
s.name = 'CodePush'
s.version = package['version'].gsub(/v|-beta/, '')
s.summary = package['description']
s.author = package['author']
s.license = package['license']
s.homepage = package['homepage']
s.source = { :git => 'https://github.com/microsoft/react-native-code-push.git', :tag => "v#{s.version}"}
s.ios.deployment_target = '15.5'
s.tvos.deployment_target = '15.5'
s.preserve_paths = '*.js'
s.library = 'z'
s.source_files = 'ios/CodePush/*.{h,m}'
s.public_header_files = ['ios/CodePush/CodePush.h']
# Note: Even though there are copy/pasted versions of some of these dependencies in the repo,
# we explicitly let CocoaPods pull in the versions below so all dependencies are resolved and
# linked properly at a parent workspace level.
s.dependency 'React-Core'
s.dependency 'SSZipArchive', '~> 2.5.5'
s.dependency 'JWT', '~> 3.0.0-beta.12'
s.dependency 'Base64', '~> 1.1'
end
================================================
FILE: Examples/CodePushDemoApp/.buckconfig
================================================
[android]
target = Google Inc.:Google APIs:23
[maven_repositories]
central = https://repo1.maven.org/maven2
================================================
FILE: Examples/CodePushDemoApp/.editorconfig
================================================
# Windows files
[*.bat]
end_of_line = crlf
================================================
FILE: Examples/CodePushDemoApp/.eslintrc.js
================================================
module.exports = {
root: true,
extends: '@react-native-community',
};
================================================
FILE: Examples/CodePushDemoApp/.flowconfig
================================================
[ignore]
; We fork some components by platform
.*/*[.]android.js
; Ignore "BUCK" generated dirs
/\.buckd/
; Ignore polyfills
node_modules/react-native/Libraries/polyfills/.*
; Flow doesn't support platforms
.*/Libraries/Utilities/LoadingView.js
[untyped]
.*/node_modules/@react-native-community/cli/.*/.*
[include]
[libs]
node_modules/react-native/interface.js
node_modules/react-native/flow/
[options]
emoji=true
exact_by_default=true
format.bracket_spacing=false
module.file_ext=.js
module.file_ext=.json
module.file_ext=.ios.js
munge_underscores=true
module.name_mapper='^react-native/\(.*\)$' -> '/node_modules/react-native/\1'
module.name_mapper='^@?[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> '/node_modules/react-native/Libraries/Image/RelativeImageStub'
suppress_type=$FlowIssue
suppress_type=$FlowFixMe
suppress_type=$FlowFixMeProps
suppress_type=$FlowFixMeState
[lints]
sketchy-null-number=warn
sketchy-null-mixed=warn
sketchy-number=warn
untyped-type-import=warn
nonstrict-import=warn
deprecated-type=warn
unsafe-getters-setters=warn
unnecessary-invariant=warn
signature-verification-failure=warn
[strict]
deprecated-type
nonstrict-import
sketchy-null
unclear-type
unsafe-getters-setters
untyped-import
untyped-type-import
[version]
^0.158.0
================================================
FILE: Examples/CodePushDemoApp/.gitattributes
================================================
# Windows files should use crlf line endings
# https://help.github.com/articles/dealing-with-line-endings/
*.bat text eol=crlf
================================================
FILE: Examples/CodePushDemoApp/.gitignore
================================================
# OSX
#
.DS_Store
# Xcode
#
build/
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata
*.xccheckout
*.moved-aside
DerivedData
*.hmap
*.ipa
*.xcuserstate
# Android/IntelliJ
#
build/
.idea
.gradle
local.properties
*.iml
*.hprof
# node.js
#
node_modules/
npm-debug.log
yarn-error.log
# BUCK
buck-out/
\.buckd/
*.keystore
!debug.keystore
# fastlane
#
# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
# screenshots whenever they are needed.
# For more information about the recommended setup visit:
# https://docs.fastlane.tools/best-practices/source-control/
*/fastlane/report.xml
*/fastlane/Preview.html
*/fastlane/screenshots
# Bundle artifact
*.jsbundle
# CocoaPods
/ios/Pods/
================================================
FILE: Examples/CodePushDemoApp/.prettierrc.js
================================================
module.exports = {
bracketSpacing: false,
jsxBracketSameLine: true,
singleQuote: true,
trailingComma: 'all',
arrowParens: 'avoid',
};
================================================
FILE: Examples/CodePushDemoApp/.watchmanconfig
================================================
{}
================================================
FILE: Examples/CodePushDemoApp/App.js
================================================
import React, { Component } from 'react';
import {
AppRegistry,
Dimensions,
Image,
StyleSheet,
Text,
TouchableOpacity,
View,
} from 'react-native';
import CodePush from "react-native-code-push";
class App extends Component<{}> {
constructor() {
super();
this.state = { restartAllowed: true };
}
codePushStatusDidChange(syncStatus) {
switch(syncStatus) {
case CodePush.SyncStatus.CHECKING_FOR_UPDATE:
this.setState({ syncMessage: "Checking for update." });
break;
case CodePush.SyncStatus.DOWNLOADING_PACKAGE:
this.setState({ syncMessage: "Downloading package." });
break;
case CodePush.SyncStatus.AWAITING_USER_ACTION:
this.setState({ syncMessage: "Awaiting user action." });
break;
case CodePush.SyncStatus.INSTALLING_UPDATE:
this.setState({ syncMessage: "Installing update." });
break;
case CodePush.SyncStatus.UP_TO_DATE:
this.setState({ syncMessage: "App up to date.", progress: false });
break;
case CodePush.SyncStatus.UPDATE_IGNORED:
this.setState({ syncMessage: "Update cancelled by user.", progress: false });
break;
case CodePush.SyncStatus.UPDATE_INSTALLED:
this.setState({ syncMessage: "Update installed and will be applied on restart.", progress: false });
break;
case CodePush.SyncStatus.UNKNOWN_ERROR:
this.setState({ syncMessage: "An unknown error occurred.", progress: false });
break;
}
}
codePushDownloadDidProgress(progress) {
this.setState({ progress });
}
toggleAllowRestart() {
this.state.restartAllowed
? CodePush.disallowRestart()
: CodePush.allowRestart();
this.setState({ restartAllowed: !this.state.restartAllowed });
}
getUpdateMetadata() {
CodePush.getUpdateMetadata(CodePush.UpdateState.RUNNING)
.then((metadata: LocalPackage) => {
this.setState({ syncMessage: metadata ? JSON.stringify(metadata) : "Running binary version", progress: false });
}, (error: any) => {
this.setState({ syncMessage: "Error: " + error, progress: false });
});
}
/** Update is downloaded silently, and applied on restart (recommended) */
sync() {
CodePush.sync(
{},
this.codePushStatusDidChange.bind(this),
this.codePushDownloadDidProgress.bind(this)
);
}
/** Update pops a confirmation dialog, and then immediately reboots the app */
syncImmediate() {
CodePush.sync(
{ installMode: CodePush.InstallMode.IMMEDIATE, updateDialog: true },
this.codePushStatusDidChange.bind(this),
this.codePushDownloadDidProgress.bind(this)
);
}
render() {
let progressView;
if (this.state.progress) {
progressView = (
{this.state.progress.receivedBytes} of {this.state.progress.totalBytes} bytes received
);
}
return (
Welcome to CodePush!
Press for background sync
Press for dialog-driven sync
{progressView}
Restart { this.state.restartAllowed ? "allowed" : "forbidden"}
Press for Update Metadata
{this.state.syncMessage || ""}
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: "center",
backgroundColor: "#F5FCFF",
paddingTop: 50
},
image: {
margin: 30,
width: Dimensions.get("window").width - 100,
height: 365 * (Dimensions.get("window").width - 100) / 651,
},
messages: {
marginTop: 30,
textAlign: "center",
},
restartToggleButton: {
color: "blue",
fontSize: 17
},
syncButton: {
color: "green",
fontSize: 17
},
welcome: {
fontSize: 20,
textAlign: "center",
margin: 20
},
});
/**
* Configured with a MANUAL check frequency for easy testing. For production apps, it is recommended to configure a
* different check frequency, such as ON_APP_START, for a 'hands-off' approach where CodePush.sync() does not
* need to be explicitly called. All options of CodePush.sync() are also available in this decorator.
*/
let codePushOptions = { checkFrequency: CodePush.CheckFrequency.MANUAL };
App = CodePush(codePushOptions)(App);
export default App;
================================================
FILE: Examples/CodePushDemoApp/__tests__/App-test.js
================================================
/**
* @format
*/
import 'react-native';
import React from 'react';
import App from '../App';
// Note: test renderer must be required after react-native.
import renderer from 'react-test-renderer';
it('renders correctly', () => {
renderer.create();
});
================================================
FILE: Examples/CodePushDemoApp/android/app/BUCK
================================================
# To learn about Buck see [Docs](https://buckbuild.com/).
# To run your application with Buck:
# - install Buck
# - `npm start` - to start the packager
# - `cd android`
# - `keytool -genkey -v -keystore keystores/debug.keystore -storepass android -alias androiddebugkey -keypass android -dname "CN=Android Debug,O=Android,C=US"`
# - `./gradlew :app:copyDownloadableDepsToLibs` - make all Gradle compile dependencies available to Buck
# - `buck install -r android/app` - compile, install and run application
#
load(":build_defs.bzl", "create_aar_targets", "create_jar_targets")
lib_deps = []
create_aar_targets(glob(["libs/*.aar"]))
create_jar_targets(glob(["libs/*.jar"]))
android_library(
name = "all-libs",
exported_deps = lib_deps,
)
android_library(
name = "app-code",
srcs = glob([
"src/main/java/**/*.java",
]),
deps = [
":all-libs",
":build_config",
":res",
],
)
android_build_config(
name = "build_config",
package = "com.codepushdemoapp",
)
android_resource(
name = "res",
package = "com.codepushdemoapp",
res = "src/main/res",
)
android_binary(
name = "app",
keystore = "//android/keystores:debug",
manifest = "src/main/AndroidManifest.xml",
package_type = "debug",
deps = [
":app-code",
],
)
================================================
FILE: Examples/CodePushDemoApp/android/app/build.gradle
================================================
apply plugin: "com.android.application"
import com.android.build.OutputFile
/**
* The react.gradle file registers a task for each build variant (e.g. bundleDebugJsAndAssets
* and bundleReleaseJsAndAssets).
* These basically call `react-native bundle` with the correct arguments during the Android build
* cycle. By default, bundleDebugJsAndAssets is skipped, as in debug/dev mode we prefer to load the
* bundle directly from the development server. Below you can see all the possible configurations
* and their defaults. If you decide to add a configuration block, make sure to add it before the
* `apply from: "../../node_modules/react-native/react.gradle"` line.
*
* project.ext.react = [
* // the name of the generated asset file containing your JS bundle
* bundleAssetName: "index.android.bundle",
*
* // the entry file for bundle generation. If none specified and
* // "index.android.js" exists, it will be used. Otherwise "index.js" is
* // default. Can be overridden with ENTRY_FILE environment variable.
* entryFile: "index.android.js",
*
* // https://reactnative.dev/docs/performance#enable-the-ram-format
* bundleCommand: "ram-bundle",
*
* // whether to bundle JS and assets in debug mode
* bundleInDebug: false,
*
* // whether to bundle JS and assets in release mode
* bundleInRelease: true,
*
* // whether to bundle JS and assets in another build variant (if configured).
* // See http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Variants
* // The configuration property can be in the following formats
* // 'bundleIn${productFlavor}${buildType}'
* // 'bundleIn${buildType}'
* // bundleInFreeDebug: true,
* // bundleInPaidRelease: true,
* // bundleInBeta: true,
*
* // whether to disable dev mode in custom build variants (by default only disabled in release)
* // for example: to disable dev mode in the staging build type (if configured)
* devDisabledInStaging: true,
* // The configuration property can be in the following formats
* // 'devDisabledIn${productFlavor}${buildType}'
* // 'devDisabledIn${buildType}'
*
* // the root of your project, i.e. where "package.json" lives
* root: "../../",
*
* // where to put the JS bundle asset in debug mode
* jsBundleDirDebug: "$buildDir/intermediates/assets/debug",
*
* // where to put the JS bundle asset in release mode
* jsBundleDirRelease: "$buildDir/intermediates/assets/release",
*
* // where to put drawable resources / React Native assets, e.g. the ones you use via
* // require('./image.png')), in debug mode
* resourcesDirDebug: "$buildDir/intermediates/res/merged/debug",
*
* // where to put drawable resources / React Native assets, e.g. the ones you use via
* // require('./image.png')), in release mode
* resourcesDirRelease: "$buildDir/intermediates/res/merged/release",
*
* // by default the gradle tasks are skipped if none of the JS files or assets change; this means
* // that we don't look at files in android/ or ios/ to determine whether the tasks are up to
* // date; if you have any other folders that you want to ignore for performance reasons (gradle
* // indexes the entire tree), add them here. Alternatively, if you have JS files in android/
* // for example, you might want to remove it from here.
* inputExcludes: ["android/**", "ios/**"],
*
* // override which node gets called and with what additional arguments
* nodeExecutableAndArgs: ["node"],
*
* // supply additional arguments to the packager
* extraPackagerArgs: []
* ]
*/
project.ext.react = [
enableHermes: false, // clean and rebuild if changing
]
apply from: "../../node_modules/react-native/react.gradle"
apply from: "../../node_modules/react-native-code-push/android/codepush.gradle"
/**
* Set this to true to create two separate APKs instead of one:
* - An APK that only works on ARM devices
* - An APK that only works on x86 devices
* The advantage is the size of the APK is reduced by about 4MB.
* Upload all the APKs to the Play Store and people will download
* the correct one based on the CPU architecture of their device.
*/
def enableSeparateBuildPerCPUArchitecture = false
/**
* Run Proguard to shrink the Java bytecode in release builds.
*/
def enableProguardInReleaseBuilds = false
/**
* The preferred build flavor of JavaScriptCore.
*
* For example, to use the international variant, you can use:
* `def jscFlavor = 'org.webkit:android-jsc-intl:+'`
*
* The international variant includes ICU i18n library and necessary data
* allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that
* give correct results when using with locales other than en-US. Note that
* this variant is about 6MiB larger per architecture than default.
*/
def jscFlavor = 'org.webkit:android-jsc:+'
/**
* Whether to enable the Hermes VM.
*
* This should be set on project.ext.react and mirrored here. If it is not set
* on project.ext.react, JavaScript will not be compiled to Hermes Bytecode
* and the benefits of using Hermes will therefore be sharply reduced.
*/
def enableHermes = project.ext.react.get("enableHermes", false);
/**
* Architectures to build native code for in debug.
*/
def nativeArchitectures = project.getProperties().get("reactNativeDebugArchitectures")
android {
ndkVersion rootProject.ext.ndkVersion
compileSdkVersion rootProject.ext.compileSdkVersion
defaultConfig {
applicationId "com.codepushdemoapp"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1
versionName "1.0"
}
splits {
abi {
reset()
enable enableSeparateBuildPerCPUArchitecture
universalApk false // If true, also generate a universal APK
include "armeabi-v7a", "x86", "arm64-v8a", "x86_64"
}
}
signingConfigs {
debug {
storeFile file('debug.keystore')
storePassword 'android'
keyAlias 'androiddebugkey'
keyPassword 'android'
}
}
buildTypes {
debug {
signingConfig signingConfigs.debug
if (nativeArchitectures) {
ndk {
abiFilters nativeArchitectures.split(',')
}
}
}
release {
// Caution! In production, you need to generate your own keystore file.
// see https://reactnative.dev/docs/signed-apk-android.
signingConfig signingConfigs.debug
minifyEnabled enableProguardInReleaseBuilds
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
}
}
// applicationVariants are e.g. debug, release
applicationVariants.all { variant ->
variant.outputs.each { output ->
// For each separate APK per architecture, set a unique version code as described here:
// https://developer.android.com/studio/build/configure-apk-splits.html
// Example: versionCode 1 will generate 1001 for armeabi-v7a, 1002 for x86, etc.
def versionCodes = ["armeabi-v7a": 1, "x86": 2, "arm64-v8a": 3, "x86_64": 4]
def abi = output.getFilter(OutputFile.ABI)
if (abi != null) { // null for the universal-debug, universal-release variants
output.versionCodeOverride =
defaultConfig.versionCode * 1000 + versionCodes.get(abi)
}
}
}
}
dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
//noinspection GradleDynamicVersion
implementation "com.facebook.react:react-native:+" // From node_modules
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0"
debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}") {
exclude group:'com.facebook.fbjni'
}
debugImplementation("com.facebook.flipper:flipper-network-plugin:${FLIPPER_VERSION}") {
exclude group:'com.facebook.flipper'
exclude group:'com.squareup.okhttp3', module:'okhttp'
}
debugImplementation("com.facebook.flipper:flipper-fresco-plugin:${FLIPPER_VERSION}") {
exclude group:'com.facebook.flipper'
}
if (enableHermes) {
def hermesPath = "../../node_modules/hermes-engine/android/";
debugImplementation files(hermesPath + "hermes-debug.aar")
releaseImplementation files(hermesPath + "hermes-release.aar")
} else {
implementation jscFlavor
}
}
// Run this once to be able to run the application with BUCK
// puts all compile dependencies into folder libs for BUCK to use
task copyDownloadableDepsToLibs(type: Copy) {
from configurations.implementation
into 'libs'
}
apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project)
================================================
FILE: Examples/CodePushDemoApp/android/app/build_defs.bzl
================================================
"""Helper definitions to glob .aar and .jar targets"""
def create_aar_targets(aarfiles):
for aarfile in aarfiles:
name = "aars__" + aarfile[aarfile.rindex("/") + 1:aarfile.rindex(".aar")]
lib_deps.append(":" + name)
android_prebuilt_aar(
name = name,
aar = aarfile,
)
def create_jar_targets(jarfiles):
for jarfile in jarfiles:
name = "jars__" + jarfile[jarfile.rindex("/") + 1:jarfile.rindex(".jar")]
lib_deps.append(":" + name)
prebuilt_jar(
name = name,
binary_jar = jarfile,
)
================================================
FILE: Examples/CodePushDemoApp/android/app/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
================================================
FILE: Examples/CodePushDemoApp/android/app/src/main/AndroidManifest.xml
================================================
================================================
FILE: Examples/CodePushDemoApp/android/app/src/main/java/com/codepushdemoapp/MainActivity.java
================================================
package com.codepushdemoapp;
import com.facebook.react.ReactActivity;
public class MainActivity extends ReactActivity {
/**
* Returns the name of the main component registered from JavaScript. This is used to schedule
* rendering of the component.
*/
@Override
protected String getMainComponentName() {
return "CodePushDemoApp";
}
}
================================================
FILE: Examples/CodePushDemoApp/android/app/src/main/java/com/codepushdemoapp/MainApplication.java
================================================
package com.codepushdemoapp;
import android.app.Application;
import com.microsoft.codepush.react.CodePush;
import android.content.Context;
import com.facebook.react.PackageList;
import com.facebook.react.ReactApplication;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactPackage;
import com.facebook.soloader.SoLoader;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
public class MainApplication extends Application implements ReactApplication {
private final ReactNativeHost mReactNativeHost =
new ReactNativeHost(this) {
@Override
protected String getJSBundleFile(){
return CodePush.getJSBundleFile();
}
@Override
public boolean getUseDeveloperSupport() {
return BuildConfig.DEBUG;
}
@Override
protected List getPackages() {
@SuppressWarnings("UnnecessaryLocalVariable")
List packages = new PackageList(this).getPackages();
// Packages that cannot be autolinked yet can be added manually here, for example:
// packages.add(new MyReactNativePackage());
return packages;
}
@Override
protected String getJSMainModuleName() {
return "index";
}
};
@Override
public ReactNativeHost getReactNativeHost() {
return mReactNativeHost;
}
@Override
public void onCreate() {
super.onCreate();
SoLoader.init(this, /* native exopackage */ false);
initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
}
/**
* Loads Flipper in React Native templates. Call this in the onCreate method with something like
* initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
*
* @param context
* @param reactInstanceManager
*/
private static void initializeFlipper(
Context context, ReactInstanceManager reactInstanceManager) {
if (BuildConfig.DEBUG) {
try {
/*
We use reflection here to pick up the class that initializes Flipper,
since Flipper library is not available in release mode
*/
Class> aClass = Class.forName("com.codepushdemoapp.ReactNativeFlipper");
aClass
.getMethod("initializeFlipper", Context.class, ReactInstanceManager.class)
.invoke(null, context, reactInstanceManager);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
}
================================================
FILE: Examples/CodePushDemoApp/android/app/src/main/res/values/strings.xml
================================================
deployment-key-here
CodePushDemoApp
================================================
FILE: Examples/CodePushDemoApp/android/app/src/main/res/values/styles.xml
================================================
================================================
FILE: Examples/CodePushDemoApp/android/build.gradle
================================================
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext {
buildToolsVersion = "30.0.2"
minSdkVersion = 21
compileSdkVersion = 30
targetSdkVersion = 30
ndkVersion = "21.4.7075529"
}
repositories {
google()
mavenCentral()
}
dependencies {
classpath("com.android.tools.build:gradle:4.2.2")
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
mavenCentral()
mavenLocal()
maven {
// All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
url("$rootDir/../node_modules/react-native/android")
}
maven {
// Android JSC is installed from npm
url("$rootDir/../node_modules/jsc-android/dist")
}
google()
maven { url 'https://www.jitpack.io' }
}
}
================================================
FILE: Examples/CodePushDemoApp/android/gradle/wrapper/gradle-wrapper.properties
================================================
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.9-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
================================================
FILE: Examples/CodePushDemoApp/android/gradle.properties
================================================
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
# Default value: -Xmx10248m -XX:MaxPermSize=256m
# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app's APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
# Automatically convert third-party libraries to use AndroidX
android.enableJetifier=true
# Version of flipper SDK to use with React Native
FLIPPER_VERSION=0.99.0
================================================
FILE: Examples/CodePushDemoApp/android/gradlew
================================================
#!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# Licensed 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
#
# https://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.
#
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
exec "$JAVACMD" "$@"
================================================
FILE: Examples/CodePushDemoApp/android/gradlew.bat
================================================
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
================================================
FILE: Examples/CodePushDemoApp/android/settings.gradle
================================================
rootProject.name = 'CodePushDemoApp'
apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings)
include ':app', ':react-native-code-push'
project(':react-native-code-push').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-code-push/android/app')
================================================
FILE: Examples/CodePushDemoApp/app.json
================================================
{
"name": "CodePushDemoApp",
"displayName": "CodePushDemoApp"
}
================================================
FILE: Examples/CodePushDemoApp/babel.config.js
================================================
module.exports = {
presets: ['module:metro-react-native-babel-preset'],
};
================================================
FILE: Examples/CodePushDemoApp/index.js
================================================
import { AppRegistry } from 'react-native';
import App from './App';
AppRegistry.registerComponent('CodePushDemoApp', () => App);
================================================
FILE: Examples/CodePushDemoApp/ios/CodePushDemoApp/AppDelegate.h
================================================
#import
#import
@interface AppDelegate : UIResponder
@property (nonatomic, strong) UIWindow *window;
@end
================================================
FILE: Examples/CodePushDemoApp/ios/CodePushDemoApp/AppDelegate.m
================================================
#import "AppDelegate.h"
#import
#import
#import
#import
#ifdef FB_SONARKIT_ENABLED
#import
#import
#import
#import
#import
#import
static void InitializeFlipper(UIApplication *application) {
FlipperClient *client = [FlipperClient sharedClient];
SKDescriptorMapper *layoutDescriptorMapper = [[SKDescriptorMapper alloc] initWithDefaults];
[client addPlugin:[[FlipperKitLayoutPlugin alloc] initWithRootNode:application withDescriptorMapper:layoutDescriptorMapper]];
[client addPlugin:[[FKUserDefaultsPlugin alloc] initWithSuiteName:nil]];
[client addPlugin:[FlipperKitReactPlugin new]];
[client addPlugin:[[FlipperKitNetworkPlugin alloc] initWithNetworkAdapter:[SKIOSNetworkAdapter new]]];
[client start];
}
#endif
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
#ifdef FB_SONARKIT_ENABLED
InitializeFlipper(application);
#endif
RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions];
RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge
moduleName:@"CodePushDemoApp"
initialProperties:nil];
if (@available(iOS 13.0, *)) {
rootView.backgroundColor = [UIColor systemBackgroundColor];
} else {
rootView.backgroundColor = [UIColor whiteColor];
}
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
UIViewController *rootViewController = [UIViewController new];
rootViewController.view = rootView;
self.window.rootViewController = rootViewController;
[self.window makeKeyAndVisible];
return YES;
}
- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
{
#if DEBUG
return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];
#else
return [CodePush bundleURL];
#endif
}
@end
================================================
FILE: Examples/CodePushDemoApp/ios/CodePushDemoApp/Images.xcassets/AppIcon.appiconset/Contents.json
================================================
{
"images" : [
{
"idiom" : "iphone",
"size" : "29x29",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "29x29",
"scale" : "3x"
},
{
"idiom" : "iphone",
"size" : "40x40",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "40x40",
"scale" : "3x"
},
{
"idiom" : "iphone",
"size" : "60x60",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "60x60",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
================================================
FILE: Examples/CodePushDemoApp/ios/CodePushDemoApp/Images.xcassets/Contents.json
================================================
{
"info" : {
"version" : 1,
"author" : "xcode"
}
}
================================================
FILE: Examples/CodePushDemoApp/ios/CodePushDemoApp/Info.plist
================================================
CFBundleDevelopmentRegion
en
CFBundleDisplayName
CodePushDemoApp
CFBundleExecutable
$(EXECUTABLE_NAME)
CFBundleIdentifier
$(PRODUCT_BUNDLE_IDENTIFIER)
CFBundleInfoDictionaryVersion
6.0
CFBundleName
$(PRODUCT_NAME)
CFBundlePackageType
APPL
CFBundleShortVersionString
1.0
CFBundleSignature
????
CFBundleVersion
1
LSRequiresIPhoneOS
NSAppTransportSecurity
NSExceptionDomains
localhost
NSExceptionAllowsInsecureHTTPLoads
NSLocationWhenInUseUsageDescription
UILaunchStoryboardName
LaunchScreen
UIRequiredDeviceCapabilities
armv7
UISupportedInterfaceOrientations
UIInterfaceOrientationPortrait
UIInterfaceOrientationLandscapeLeft
UIInterfaceOrientationLandscapeRight
UIViewControllerBasedStatusBarAppearance
CodePushDeploymentKey
deployment-key-here
================================================
FILE: Examples/CodePushDemoApp/ios/CodePushDemoApp/LaunchScreen.storyboard
================================================
================================================
FILE: Examples/CodePushDemoApp/ios/CodePushDemoApp/main.m
================================================
#import
#import "AppDelegate.h"
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
================================================
FILE: Examples/CodePushDemoApp/ios/CodePushDemoApp.xcodeproj/project.pbxproj
================================================
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 54;
objects = {
/* Begin PBXBuildFile section */
00E356F31AD99517003FC87E /* CodePushDemoAppTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 00E356F21AD99517003FC87E /* CodePushDemoAppTests.m */; };
13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.m */; };
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; };
7209CF15AFD1B1A1154ADDC3 /* libPods-CodePushDemoApp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = F822409B61CE9E11C825CB2E /* libPods-CodePushDemoApp.a */; };
81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; };
873543544E84903AB9AC764D /* libPods-CodePushDemoApp-CodePushDemoAppTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 0684492BF481E6EAA4A6624A /* libPods-CodePushDemoApp-CodePushDemoAppTests.a */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
00E356F41AD99517003FC87E /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 13B07F861A680F5B00A75B9A;
remoteInfo = CodePushDemoApp;
};
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
00E356EE1AD99517003FC87E /* CodePushDemoAppTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CodePushDemoAppTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
00E356F11AD99517003FC87E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
00E356F21AD99517003FC87E /* CodePushDemoAppTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CodePushDemoAppTests.m; sourceTree = ""; };
0684492BF481E6EAA4A6624A /* libPods-CodePushDemoApp-CodePushDemoAppTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-CodePushDemoApp-CodePushDemoAppTests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
13B07F961A680F5B00A75B9A /* CodePushDemoApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CodePushDemoApp.app; sourceTree = BUILT_PRODUCTS_DIR; };
13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = CodePushDemoApp/AppDelegate.h; sourceTree = ""; };
13B07FB01A68108700A75B9A /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AppDelegate.m; path = CodePushDemoApp/AppDelegate.m; sourceTree = ""; };
13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = CodePushDemoApp/Images.xcassets; sourceTree = ""; };
13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = CodePushDemoApp/Info.plist; sourceTree = ""; };
13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = CodePushDemoApp/main.m; sourceTree = ""; };
3A1AC7C53CE0CC5BA2B85E4E /* Pods-CodePushDemoApp.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CodePushDemoApp.debug.xcconfig"; path = "Target Support Files/Pods-CodePushDemoApp/Pods-CodePushDemoApp.debug.xcconfig"; sourceTree = ""; };
3FF5216EA67271BFA35BE5DD /* Pods-CodePushDemoApp-CodePushDemoAppTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CodePushDemoApp-CodePushDemoAppTests.debug.xcconfig"; path = "Target Support Files/Pods-CodePushDemoApp-CodePushDemoAppTests/Pods-CodePushDemoApp-CodePushDemoAppTests.debug.xcconfig"; sourceTree = ""; };
4B6E53AD2EAAA824D962A672 /* Pods-CodePushDemoApp.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CodePushDemoApp.release.xcconfig"; path = "Target Support Files/Pods-CodePushDemoApp/Pods-CodePushDemoApp.release.xcconfig"; sourceTree = ""; };
81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = CodePushDemoApp/LaunchScreen.storyboard; sourceTree = ""; };
9E42C477F1442EBF73599739 /* Pods-CodePushDemoApp-CodePushDemoAppTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CodePushDemoApp-CodePushDemoAppTests.release.xcconfig"; path = "Target Support Files/Pods-CodePushDemoApp-CodePushDemoAppTests/Pods-CodePushDemoApp-CodePushDemoAppTests.release.xcconfig"; sourceTree = ""; };
ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };
F822409B61CE9E11C825CB2E /* libPods-CodePushDemoApp.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-CodePushDemoApp.a"; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
00E356EB1AD99517003FC87E /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
873543544E84903AB9AC764D /* libPods-CodePushDemoApp-CodePushDemoAppTests.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
13B07F8C1A680F5B00A75B9A /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
7209CF15AFD1B1A1154ADDC3 /* libPods-CodePushDemoApp.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
00E356EF1AD99517003FC87E /* CodePushDemoAppTests */ = {
isa = PBXGroup;
children = (
00E356F21AD99517003FC87E /* CodePushDemoAppTests.m */,
00E356F01AD99517003FC87E /* Supporting Files */,
);
path = CodePushDemoAppTests;
sourceTree = "";
};
00E356F01AD99517003FC87E /* Supporting Files */ = {
isa = PBXGroup;
children = (
00E356F11AD99517003FC87E /* Info.plist */,
);
name = "Supporting Files";
sourceTree = "";
};
13B07FAE1A68108700A75B9A /* CodePushDemoApp */ = {
isa = PBXGroup;
children = (
13B07FAF1A68108700A75B9A /* AppDelegate.h */,
13B07FB01A68108700A75B9A /* AppDelegate.m */,
13B07FB51A68108700A75B9A /* Images.xcassets */,
13B07FB61A68108700A75B9A /* Info.plist */,
81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */,
13B07FB71A68108700A75B9A /* main.m */,
);
name = CodePushDemoApp;
sourceTree = "";
};
2D16E6871FA4F8E400B85C8A /* Frameworks */ = {
isa = PBXGroup;
children = (
ED297162215061F000B7C4FE /* JavaScriptCore.framework */,
F822409B61CE9E11C825CB2E /* libPods-CodePushDemoApp.a */,
0684492BF481E6EAA4A6624A /* libPods-CodePushDemoApp-CodePushDemoAppTests.a */,
);
name = Frameworks;
sourceTree = "";
};
69F6BBD5C7690E30A982EAEE /* Pods */ = {
isa = PBXGroup;
children = (
3A1AC7C53CE0CC5BA2B85E4E /* Pods-CodePushDemoApp.debug.xcconfig */,
4B6E53AD2EAAA824D962A672 /* Pods-CodePushDemoApp.release.xcconfig */,
3FF5216EA67271BFA35BE5DD /* Pods-CodePushDemoApp-CodePushDemoAppTests.debug.xcconfig */,
9E42C477F1442EBF73599739 /* Pods-CodePushDemoApp-CodePushDemoAppTests.release.xcconfig */,
);
name = Pods;
path = Pods;
sourceTree = "";
};
832341AE1AAA6A7D00B99B32 /* Libraries */ = {
isa = PBXGroup;
children = (
);
name = Libraries;
sourceTree = "";
};
83CBB9F61A601CBA00E9B192 = {
isa = PBXGroup;
children = (
13B07FAE1A68108700A75B9A /* CodePushDemoApp */,
832341AE1AAA6A7D00B99B32 /* Libraries */,
00E356EF1AD99517003FC87E /* CodePushDemoAppTests */,
83CBBA001A601CBA00E9B192 /* Products */,
2D16E6871FA4F8E400B85C8A /* Frameworks */,
69F6BBD5C7690E30A982EAEE /* Pods */,
);
indentWidth = 2;
sourceTree = "";
tabWidth = 2;
usesTabs = 0;
};
83CBBA001A601CBA00E9B192 /* Products */ = {
isa = PBXGroup;
children = (
13B07F961A680F5B00A75B9A /* CodePushDemoApp.app */,
00E356EE1AD99517003FC87E /* CodePushDemoAppTests.xctest */,
);
name = Products;
sourceTree = "";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
00E356ED1AD99517003FC87E /* CodePushDemoAppTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 00E357021AD99517003FC87E /* Build configuration list for PBXNativeTarget "CodePushDemoAppTests" */;
buildPhases = (
E0C08DC1172810CC2B651913 /* [CP] Check Pods Manifest.lock */,
00E356EA1AD99517003FC87E /* Sources */,
00E356EB1AD99517003FC87E /* Frameworks */,
00E356EC1AD99517003FC87E /* Resources */,
96DA1F0133CE6AECB818D1F3 /* [CP] Embed Pods Frameworks */,
8FCA0FCABC69767E741BB8F2 /* [CP] Copy Pods Resources */,
);
buildRules = (
);
dependencies = (
00E356F51AD99517003FC87E /* PBXTargetDependency */,
);
name = CodePushDemoAppTests;
productName = CodePushDemoAppTests;
productReference = 00E356EE1AD99517003FC87E /* CodePushDemoAppTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
13B07F861A680F5B00A75B9A /* CodePushDemoApp */ = {
isa = PBXNativeTarget;
buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "CodePushDemoApp" */;
buildPhases = (
FAE53E3C42A14CE48C4E62D3 /* [CP] Check Pods Manifest.lock */,
FD10A7F022414F080027D42C /* Start Packager */,
13B07F871A680F5B00A75B9A /* Sources */,
13B07F8C1A680F5B00A75B9A /* Frameworks */,
13B07F8E1A680F5B00A75B9A /* Resources */,
00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */,
3FB4CE61D43BB948153CCAD2 /* [CP] Embed Pods Frameworks */,
E64717738EB438ACC77F81BB /* [CP] Copy Pods Resources */,
);
buildRules = (
);
dependencies = (
);
name = CodePushDemoApp;
productName = CodePushDemoApp;
productReference = 13B07F961A680F5B00A75B9A /* CodePushDemoApp.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
83CBB9F71A601CBA00E9B192 /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 1210;
TargetAttributes = {
00E356ED1AD99517003FC87E = {
CreatedOnToolsVersion = 6.2;
TestTargetID = 13B07F861A680F5B00A75B9A;
};
13B07F861A680F5B00A75B9A = {
LastSwiftMigration = 1120;
};
};
};
buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "CodePushDemoApp" */;
compatibilityVersion = "Xcode 12.0";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 83CBB9F61A601CBA00E9B192;
productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
13B07F861A680F5B00A75B9A /* CodePushDemoApp */,
00E356ED1AD99517003FC87E /* CodePushDemoAppTests */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
00E356EC1AD99517003FC87E /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
13B07F8E1A680F5B00A75B9A /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */,
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Bundle React Native code and images";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "set -e\n\nexport NODE_BINARY=node\n../node_modules/react-native/scripts/react-native-xcode.sh\n";
};
3FB4CE61D43BB948153CCAD2 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-CodePushDemoApp/Pods-CodePushDemoApp-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-CodePushDemoApp/Pods-CodePushDemoApp-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-CodePushDemoApp/Pods-CodePushDemoApp-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
8FCA0FCABC69767E741BB8F2 /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-CodePushDemoApp-CodePushDemoAppTests/Pods-CodePushDemoApp-CodePushDemoAppTests-resources-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Copy Pods Resources";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-CodePushDemoApp-CodePushDemoAppTests/Pods-CodePushDemoApp-CodePushDemoAppTests-resources-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-CodePushDemoApp-CodePushDemoAppTests/Pods-CodePushDemoApp-CodePushDemoAppTests-resources.sh\"\n";
showEnvVarsInLog = 0;
};
96DA1F0133CE6AECB818D1F3 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-CodePushDemoApp-CodePushDemoAppTests/Pods-CodePushDemoApp-CodePushDemoAppTests-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-CodePushDemoApp-CodePushDemoAppTests/Pods-CodePushDemoApp-CodePushDemoAppTests-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-CodePushDemoApp-CodePushDemoAppTests/Pods-CodePushDemoApp-CodePushDemoAppTests-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
E0C08DC1172810CC2B651913 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-CodePushDemoApp-CodePushDemoAppTests-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
E64717738EB438ACC77F81BB /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-CodePushDemoApp/Pods-CodePushDemoApp-resources-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Copy Pods Resources";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-CodePushDemoApp/Pods-CodePushDemoApp-resources-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-CodePushDemoApp/Pods-CodePushDemoApp-resources.sh\"\n";
showEnvVarsInLog = 0;
};
FAE53E3C42A14CE48C4E62D3 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-CodePushDemoApp-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
FD10A7F022414F080027D42C /* Start Packager */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
name = "Start Packager";
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "export RCT_METRO_PORT=\"${RCT_METRO_PORT:=8081}\"\necho \"export RCT_METRO_PORT=${RCT_METRO_PORT}\" > \"${SRCROOT}/../node_modules/react-native/scripts/.packager.env\"\nif [ -z \"${RCT_NO_LAUNCH_PACKAGER+xxx}\" ] ; then\n if nc -w 5 -z localhost ${RCT_METRO_PORT} ; then\n if ! curl -s \"http://localhost:${RCT_METRO_PORT}/status\" | grep -q \"packager-status:running\" ; then\n echo \"Port ${RCT_METRO_PORT} already in use, packager is either not running or not running correctly\"\n exit 2\n fi\n else\n open \"$SRCROOT/../node_modules/react-native/scripts/launchPackager.command\" || echo \"Can't start packager automatically\"\n fi\nfi\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
00E356EA1AD99517003FC87E /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
00E356F31AD99517003FC87E /* CodePushDemoAppTests.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
13B07F871A680F5B00A75B9A /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */,
13B07FC11A68108700A75B9A /* main.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
00E356F51AD99517003FC87E /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 13B07F861A680F5B00A75B9A /* CodePushDemoApp */;
targetProxy = 00E356F41AD99517003FC87E /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin XCBuildConfiguration section */
00E356F61AD99517003FC87E /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 3FF5216EA67271BFA35BE5DD /* Pods-CodePushDemoApp-CodePushDemoAppTests.debug.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
INFOPLIST_FILE = CodePushDemoAppTests/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
OTHER_LDFLAGS = (
"-ObjC",
"-lc++",
"$(inherited)",
);
PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = "$(TARGET_NAME)";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/CodePushDemoApp.app/CodePushDemoApp";
};
name = Debug;
};
00E356F71AD99517003FC87E /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9E42C477F1442EBF73599739 /* Pods-CodePushDemoApp-CodePushDemoAppTests.release.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
COPY_PHASE_STRIP = NO;
INFOPLIST_FILE = CodePushDemoAppTests/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
OTHER_LDFLAGS = (
"-ObjC",
"-lc++",
"$(inherited)",
);
PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = "$(TARGET_NAME)";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/CodePushDemoApp.app/CodePushDemoApp";
};
name = Release;
};
13B07F941A680F5B00A75B9A /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 3A1AC7C53CE0CC5BA2B85E4E /* Pods-CodePushDemoApp.debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = 1;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = CodePushDemoApp/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
"-lc++",
);
PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = CodePushDemoApp;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Debug;
};
13B07F951A680F5B00A75B9A /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 4B6E53AD2EAAA824D962A672 /* Pods-CodePushDemoApp.release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = 1;
INFOPLIST_FILE = CodePushDemoApp/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
"-lc++",
);
PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = CodePushDemoApp;
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Release;
};
83CBBA201A601CBA00E9B192 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "arm64 ";
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
LD_RUNPATH_SEARCH_PATHS = (
/usr/lib/swift,
"$(inherited)",
);
LIBRARY_SEARCH_PATHS = (
"\"$(SDKROOT)/usr/lib/swift\"",
"\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\"",
"\"$(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)\"",
"\"$(inherited)\"",
);
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
};
name = Debug;
};
83CBBA211A601CBA00E9B192 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = YES;
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "arm64 ";
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
LD_RUNPATH_SEARCH_PATHS = (
/usr/lib/swift,
"$(inherited)",
);
LIBRARY_SEARCH_PATHS = (
"\"$(SDKROOT)/usr/lib/swift\"",
"\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\"",
"\"$(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)\"",
"\"$(inherited)\"",
);
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
VALIDATE_PRODUCT = YES;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
00E357021AD99517003FC87E /* Build configuration list for PBXNativeTarget "CodePushDemoAppTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
00E356F61AD99517003FC87E /* Debug */,
00E356F71AD99517003FC87E /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "CodePushDemoApp" */ = {
isa = XCConfigurationList;
buildConfigurations = (
13B07F941A680F5B00A75B9A /* Debug */,
13B07F951A680F5B00A75B9A /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "CodePushDemoApp" */ = {
isa = XCConfigurationList;
buildConfigurations = (
83CBBA201A601CBA00E9B192 /* Debug */,
83CBBA211A601CBA00E9B192 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 83CBB9F71A601CBA00E9B192 /* Project object */;
}
================================================
FILE: Examples/CodePushDemoApp/ios/CodePushDemoApp.xcodeproj/xcshareddata/xcschemes/CodePushDemoApp.xcscheme
================================================
================================================
FILE: Examples/CodePushDemoApp/ios/CodePushDemoApp.xcworkspace/contents.xcworkspacedata
================================================
================================================
FILE: Examples/CodePushDemoApp/ios/CodePushDemoApp.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
================================================
IDEDidComputeMac32BitWarning
================================================
FILE: Examples/CodePushDemoApp/ios/CodePushDemoAppTests/CodePushDemoAppTests.m
================================================
#import
#import
#import
#import
#define TIMEOUT_SECONDS 600
#define TEXT_TO_LOOK_FOR @"Welcome to React"
@interface CodePushDemoAppTests : XCTestCase
@end
@implementation CodePushDemoAppTests
- (BOOL)findSubviewInView:(UIView *)view matching:(BOOL(^)(UIView *view))test
{
if (test(view)) {
return YES;
}
for (UIView *subview in [view subviews]) {
if ([self findSubviewInView:subview matching:test]) {
return YES;
}
}
return NO;
}
- (void)testRendersWelcomeScreen
{
UIViewController *vc = [[[RCTSharedApplication() delegate] window] rootViewController];
NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS];
BOOL foundElement = NO;
__block NSString *redboxError = nil;
#ifdef DEBUG
RCTSetLogFunction(^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) {
if (level >= RCTLogLevelError) {
redboxError = message;
}
});
#endif
while ([date timeIntervalSinceNow] > 0 && !foundElement && !redboxError) {
[[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
[[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
foundElement = [self findSubviewInView:vc.view matching:^BOOL(UIView *view) {
if ([view.accessibilityLabel isEqualToString:TEXT_TO_LOOK_FOR]) {
return YES;
}
return NO;
}];
}
#ifdef DEBUG
RCTSetLogFunction(RCTDefaultLogFunction);
#endif
XCTAssertNil(redboxError, @"RedBox error: %@", redboxError);
XCTAssertTrue(foundElement, @"Couldn't find element with text '%@' in %d seconds", TEXT_TO_LOOK_FOR, TIMEOUT_SECONDS);
}
@end
================================================
FILE: Examples/CodePushDemoApp/ios/CodePushDemoAppTests/Info.plist
================================================
CFBundleDevelopmentRegion
en
CFBundleExecutable
$(EXECUTABLE_NAME)
CFBundleIdentifier
$(PRODUCT_BUNDLE_IDENTIFIER)
CFBundleInfoDictionaryVersion
6.0
CFBundleName
$(PRODUCT_NAME)
CFBundlePackageType
BNDL
CFBundleShortVersionString
1.0
CFBundleSignature
????
CFBundleVersion
1
================================================
FILE: Examples/CodePushDemoApp/ios/Podfile
================================================
require_relative '../node_modules/react-native/scripts/react_native_pods'
require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules'
platform :ios, '11.0'
target 'CodePushDemoApp' do
config = use_native_modules!
use_react_native!(
:path => config[:reactNativePath],
# to enable hermes on iOS, change `false` to `true` and then install pods
:hermes_enabled => false
)
target 'CodePushDemoAppTests' do
inherit! :complete
# Pods for testing
end
# Enables Flipper.
#
# Note that if you have use_frameworks! enabled, Flipper will not work and
# you should disable the next line.
use_flipper!()
post_install do |installer|
react_native_post_install(installer)
__apply_Xcode_12_5_M1_post_install_workaround(installer)
end
end
================================================
FILE: Examples/CodePushDemoApp/metro.config.js
================================================
/**
* Metro configuration for React Native
* https://github.com/facebook/react-native
*
* @format
*/
module.exports = {
transformer: {
getTransformOptions: async () => ({
transform: {
experimentalImportSupport: false,
inlineRequires: true,
},
}),
},
};
================================================
FILE: Examples/CodePushDemoApp/package.json
================================================
{
"name": "CodePushDemoApp",
"version": "0.0.1",
"private": true,
"scripts": {
"android": "react-native run-android",
"ios": "react-native run-ios",
"start": "react-native start",
"test": "jest",
"lint": "eslint ."
},
"dependencies": {
"react": "17.0.2",
"react-native": "0.68.5",
"react-native-code-push": "8.1.0"
},
"resolutions": {
"strip-ansi": "^6.0.1",
"ansi-regex": "^5.0.1"
},
"devDependencies": {
"@babel/core": "^7.15.8",
"@babel/runtime": "^7.15.4",
"@react-native-community/eslint-config": "^3.0.1",
"babel-jest": "^27.2.5",
"eslint": "^8.0.0",
"jest": "^27.2.5",
"metro-react-native-babel-preset": "^0.66.2",
"react-test-renderer": "17.0.2"
},
"jest": {
"preset": "react-native"
}
}
================================================
FILE: Examples/CodePushDemoAppCpp/.buckconfig
================================================
[android]
target = Google Inc.:Google APIs:23
[maven_repositories]
central = https://repo1.maven.org/maven2
================================================
FILE: Examples/CodePushDemoAppCpp/.eslintrc.js
================================================
module.exports = {
root: true,
extends: '@react-native-community',
};
================================================
FILE: Examples/CodePushDemoAppCpp/.flowconfig
================================================
[ignore]
; We fork some components by platform
.*/*[.]android.js
; Ignore "BUCK" generated dirs
/\.buckd/
; Ignore polyfills
node_modules/react-native/Libraries/polyfills/.*
; These should not be required directly
; require from fbjs/lib instead: require('fbjs/lib/warning')
node_modules/warning/.*
; Flow doesn't support platforms
.*/Libraries/Utilities/LoadingView.js
[untyped]
.*/node_modules/@react-native-community/cli/.*/.*
[include]
[libs]
node_modules/react-native/interface.js
node_modules/react-native/flow/
[options]
emoji=true
esproposal.optional_chaining=enable
esproposal.nullish_coalescing=enable
module.file_ext=.js
module.file_ext=.json
module.file_ext=.ios.js
munge_underscores=true
module.name_mapper='^react-native/\(.*\)$' -> '/node_modules/react-native/\1'
module.name_mapper='^@?[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> '/node_modules/react-native/Libraries/Image/RelativeImageStub'
suppress_type=$FlowIssue
suppress_type=$FlowFixMe
suppress_type=$FlowFixMeProps
suppress_type=$FlowFixMeState
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(\\)? *\\(site=[a-z,_]*react_native\\(_ios\\)?_\\(oss\\|fb\\)[a-z,_]*\\)?)\\)
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(\\)? *\\(site=[a-z,_]*react_native\\(_ios\\)?_\\(oss\\|fb\\)[a-z,_]*\\)?)\\)?:? #[0-9]+
suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError
[lints]
sketchy-null-number=warn
sketchy-null-mixed=warn
sketchy-number=warn
untyped-type-import=warn
nonstrict-import=warn
deprecated-type=warn
unsafe-getters-setters=warn
unnecessary-invariant=warn
signature-verification-failure=warn
deprecated-utility=error
[strict]
deprecated-type
nonstrict-import
sketchy-null
unclear-type
unsafe-getters-setters
untyped-import
untyped-type-import
[version]
^0.122.0
================================================
FILE: Examples/CodePushDemoAppCpp/.gitattributes
================================================
*.pbxproj -text
================================================
FILE: Examples/CodePushDemoAppCpp/.gitignore
================================================
# OSX
#
.DS_Store
# Xcode
#
build/
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata
*.xccheckout
*.moved-aside
DerivedData
*.hmap
*.ipa
*.xcuserstate
# Android/IntelliJ
#
build/
.idea
.gradle
local.properties
*.iml
# Visual Studio Code
#
.vscode/
# node.js
#
node_modules/
npm-debug.log
yarn-error.log
# BUCK
buck-out/
\.buckd/
*.keystore
!debug.keystore
# fastlane
#
# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
# screenshots whenever they are needed.
# For more information about the recommended setup visit:
# https://docs.fastlane.tools/best-practices/source-control/
*/fastlane/report.xml
*/fastlane/Preview.html
*/fastlane/screenshots
# Bundle artifact
*.jsbundle
# CocoaPods
/ios/Pods/
================================================
FILE: Examples/CodePushDemoAppCpp/.prettierrc.js
================================================
module.exports = {
bracketSpacing: false,
jsxBracketSameLine: true,
singleQuote: true,
trailingComma: 'all',
};
================================================
FILE: Examples/CodePushDemoAppCpp/.watchmanconfig
================================================
{}
================================================
FILE: Examples/CodePushDemoAppCpp/App.js
================================================
import React, { Component } from 'react';
import {
AppRegistry,
Dimensions,
Image,
SafeAreaView,
ScrollView,
StyleSheet,
Text,
TouchableOpacity,
View,
} from 'react-native';
import CodePush from "react-native-code-push";
class App extends Component<{}> {
constructor() {
super();
this.state = { restartAllowed: true };
}
codePushStatusDidChange(syncStatus) {
switch(syncStatus) {
case CodePush.SyncStatus.CHECKING_FOR_UPDATE:
this.setState({ syncMessage: "Checking for update." });
break;
case CodePush.SyncStatus.DOWNLOADING_PACKAGE:
this.setState({ syncMessage: "Downloading package." });
break;
case CodePush.SyncStatus.AWAITING_USER_ACTION:
this.setState({ syncMessage: "Awaiting user action." });
break;
case CodePush.SyncStatus.INSTALLING_UPDATE:
this.setState({ syncMessage: "Installing update." });
break;
case CodePush.SyncStatus.UP_TO_DATE:
this.setState({ syncMessage: "App up to date.", progress: false });
break;
case CodePush.SyncStatus.UPDATE_IGNORED:
this.setState({ syncMessage: "Update cancelled by user.", progress: false });
break;
case CodePush.SyncStatus.UPDATE_INSTALLED:
this.setState({ syncMessage: "Update installed and will be applied on restart.", progress: false });
break;
case CodePush.SyncStatus.UNKNOWN_ERROR:
this.setState({ syncMessage: "An unknown error occurred.", progress: false });
break;
}
}
codePushDownloadDidProgress(progress) {
this.setState({ progress });
}
toggleAllowRestart() {
this.state.restartAllowed
? CodePush.disallowRestart()
: CodePush.allowRestart();
this.setState({ restartAllowed: !this.state.restartAllowed });
}
getUpdateMetadata() {
CodePush.getUpdateMetadata(CodePush.UpdateState.RUNNING)
.then((metadata: LocalPackage) => {
this.setState({ syncMessage: metadata ? JSON.stringify(metadata) : "Running binary version", progress: false });
}, (error: any) => {
this.setState({ syncMessage: "Error: " + error, progress: false });
});
}
/** Update is downloaded silently, and applied on restart (recommended) */
sync() {
CodePush.sync(
{},
this.codePushStatusDidChange.bind(this),
this.codePushDownloadDidProgress.bind(this)
);
}
/** Update pops a confirmation dialog, and then immediately reboots the app */
syncImmediate() {
CodePush.sync(
{ installMode: CodePush.InstallMode.IMMEDIATE, updateDialog: true },
this.codePushStatusDidChange.bind(this),
this.codePushDownloadDidProgress.bind(this)
);
}
restartApp() {
CodePush.restartApp();
}
render() {
let progressView;
if (this.state.progress) {
progressView = (
{this.state.progress.receivedBytes} of {this.state.progress.totalBytes} bytes received
);
}
return (
Welcome to CodePush!
Demo bundle
Press to restart
Press for background sync
Press for dialog-driven sync
{progressView}
Restart { this.state.restartAllowed ? "allowed" : "forbidden"}
Press for Update Metadata
{this.state.syncMessage || ""}
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: "center",
backgroundColor: "#F5FCFF",
paddingTop: 50
},
image: {
margin: 30,
width: Dimensions.get("window").width - 100,
height: 365 * (Dimensions.get("window").width - 100) / 651,
},
messages: {
marginTop: 30,
textAlign: "center",
},
restartToggleButton: {
color: "blue",
fontSize: 17
},
syncButton: {
color: "green",
fontSize: 17
},
welcome: {
fontSize: 20,
textAlign: "center",
margin: 20
},
});
/**
* Configured with a MANUAL check frequency for easy testing. For production apps, it is recommended to configure a
* different check frequency, such as ON_APP_START, for a 'hands-off' approach where CodePush.sync() does not
* need to be explicitly called. All options of CodePush.sync() are also available in this decorator.
*/
let codePushOptions = { checkFrequency: CodePush.CheckFrequency.MANUAL };
App = CodePush(codePushOptions)(App);
export default App;
================================================
FILE: Examples/CodePushDemoAppCpp/app.json
================================================
{
"name": "CodePushDemoAppCpp",
"displayName": "CodePushDemoAppCpp"
}
================================================
FILE: Examples/CodePushDemoAppCpp/babel.config.js
================================================
module.exports = {
presets: ['module:metro-react-native-babel-preset'],
};
================================================
FILE: Examples/CodePushDemoAppCpp/index.js
================================================
/**
* @format
*/
import {AppRegistry} from 'react-native';
import App from './App';
import {name as appName} from './app.json';
AppRegistry.registerComponent(appName, () => App);
================================================
FILE: Examples/CodePushDemoAppCpp/metro.config.js
================================================
/**
* Metro configuration for React Native
* https://github.com/facebook/react-native
*
* @format
*/
const path = require('path');
const blacklist = require('metro-config/src/defaults/blacklist');
module.exports = {
resolver: {
blacklistRE: blacklist([
// This stops "react-native run-windows" from causing the metro server to crash if its already running
new RegExp(
`${path.resolve(__dirname, 'windows').replace(/[/\\]/g, '/')}.*`,
),
// This prevents "react-native run-windows" from hitting: EBUSY: resource busy or locked, open msbuild.ProjectImports.zip
/.*\.ProjectImports\.zip/,
]),
},
transformer: {
getTransformOptions: async () => ({
transform: {
experimentalImportSupport: false,
inlineRequires: false,
},
}),
},
};
================================================
FILE: Examples/CodePushDemoAppCpp/package.json
================================================
{
"name": "CodePushDemoAppCpp",
"version": "0.0.1",
"private": true,
"scripts": {
"start": "react-native start",
"test": "jest",
"lint": "eslint .",
"windows": "react-native run-windows"
},
"dependencies": {
"react": "^17.0.2",
"react-native": "^0.68.5",
"react-native-code-push": "^8.1.0",
"react-native-windows": "^1.0.0"
},
"resolutions": {
"strip-ansi": "^6.0.1",
"ansi-regex": "^5.0.1",
"node-fetch": "^2.6.7"
},
"devDependencies": {
"@babel/core": "^7.8.4",
"@babel/runtime": "^7.8.4",
"@react-native-community/eslint-config": "^3.0.1",
"appcenter-cli": "^2.14.0",
"babel-jest": "^27.2.5",
"eslint": "^8.0.0",
"jest": "^27.2.5",
"metro-react-native-babel-preset": "^0.66.2",
"react-test-renderer": "17.0.2"
},
"jest": {
"preset": "react-native"
}
}
================================================
FILE: Examples/CodePushDemoAppCpp/windows/.gitignore
================================================
*AppPackages*
*BundleArtifacts*
#OS junk files
[Tt]humbs.db
*.DS_Store
#Visual Studio files
*.[Oo]bj
*.user
*.aps
*.pch
*.vspscc
*.vssscc
*_i.c
*_p.c
*.ncb
*.suo
*.tlb
*.tlh
*.bak
*.[Cc]ache
*.ilk
*.log
*.lib
*.sbr
*.sdf
*.opensdf
*.opendb
*.unsuccessfulbuild
ipch/
[Oo]bj/
[Bb]in
[Dd]ebug*/
[Rr]elease*/
Ankh.NoLoad
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
#MonoDevelop
*.pidb
*.userprefs
#Tooling
_ReSharper*/
*.resharper
[Tt]est[Rr]esult*
*.sass-cache
#Project files
[Bb]uild/
#Subversion files
.svn
# Office Temp Files
~$*
# vim Temp Files
*~
#NuGet
packages/
*.nupkg
#ncrunch
*ncrunch*
*crunch*.local.xml
# visual studio database projects
*.dbmdl
#Test files
*.testsettings
#Other files
*.DotSettings
.vs/
*project.lock.json
#Files generated by the VS build
**/Generated Files/**
================================================
FILE: Examples/CodePushDemoAppCpp/windows/CodePushDemoAppCpp/.gitignore
================================================
/Bundle
================================================
FILE: Examples/CodePushDemoAppCpp/windows/CodePushDemoAppCpp/App.cpp
================================================
#include "pch.h"
#include "App.h"
#include "AutolinkedNativeModules.g.h"
#include "ReactPackageProvider.h"
#include "winrt/Microsoft.CodePush.ReactNative.h"
#include "winrt/Windows.Storage.h"
#include "winrt/Windows.Foundation.h"
#include "winrt/Windows.Data.Json.h"
#include
using namespace winrt::CodePushDemoAppCpp;
using namespace winrt::CodePushDemoAppCpp::implementation;
using namespace winrt;
using namespace Windows::UI::Xaml;
using namespace Windows::UI::Xaml::Controls;
using namespace Windows::UI::Xaml::Navigation;
using namespace Windows::ApplicationModel;
using namespace Windows::ApplicationModel::Activation;
using namespace Windows::Storage;
using namespace Windows::Data::Json;
using namespace Microsoft::ReactNative;
using namespace std;
///
/// Initializes the singleton application object. This is the first line of
/// authored code executed, and as such is the logical equivalent of main() or
/// WinMain().
///
App::App() noexcept
{
#if BUNDLE
m_host.InstanceSettings().JavaScriptBundleFile(L"index.windows");
m_host.InstanceSettings().UseWebDebugger(false);
m_host.InstanceSettings().UseFastRefresh(false);
#else
m_host.InstanceSettings().JavaScriptMainModuleName(L"index");
m_host.InstanceSettings().UseWebDebugger(true);
m_host.InstanceSettings().UseFastRefresh(true);
#endif
#if _DEBUG
m_host.InstanceSettings().UseDeveloperSupport(true);
#else
m_host.InstanceSettings().UseDeveloperSupport(false);
#endif
RegisterAutolinkedNativeModulePackages(m_host.InstanceSettings().PackageProviders()); // Includes any autolinked modules
m_host.InstanceSettings().PackageProviders().Append(make()); // Includes all modules in this project
InitializeComponent();
Suspending({ this, &App::OnSuspending });
#if defined _DEBUG && !defined DISABLE_XAML_GENERATED_BREAK_ON_UNHANDLED_EXCEPTION
UnhandledException([this](IInspectable const&, UnhandledExceptionEventArgs const& e)
{
if (IsDebuggerPresent())
{
auto errorMessage = e.Message();
__debugbreak();
}
});
#endif
}
///
/// Invoked when the application is launched normally by the end user. Other entry points
/// will be used such as when the application is launched to open a specific file.
///
/// Details about the launch request and process.
void App::OnLaunched(activation::LaunchActivatedEventArgs const& e)
{
winrt::Microsoft::CodePush::ReactNative::CodePushConfig::SetHost(Host());
auto configMap{ winrt::single_threaded_map() };
configMap.Insert(L"appVersion", L"1.0.0");
configMap.Insert(L"deploymentKey", L"");
winrt::Microsoft::CodePush::ReactNative::CodePushConfig::Init(configMap);
Frame rootFrame{ nullptr };
auto content = Window::Current().Content();
if (content)
{
rootFrame = content.try_as();
}
// Do not repeat app initialization when the Window already has content,
// just ensure that the window is active
if (rootFrame == nullptr)
{
// Create a Frame to act as the navigation context and associate it with
// a SuspensionManager key
rootFrame = Frame();
rootFrame.NavigationFailed({ this, &App::OnNavigationFailed });
if (e.PreviousExecutionState() == ApplicationExecutionState::Terminated)
{
// Restore the saved session state only when appropriate, scheduling the
// final launch steps after the restore is complete
}
if (e.PrelaunchActivated() == false)
{
if (rootFrame.Content() == nullptr)
{
// When the navigation stack isn't restored navigate to the first page,
// configuring the new page by passing required information as a navigation
// parameter
rootFrame.Navigate(xaml_typename(), box_value(e.Arguments()));
}
// Place the frame in the current Window
Window::Current().Content(rootFrame);
// Ensure the current window is active
Window::Current().Activate();
}
}
else
{
if (e.PrelaunchActivated() == false)
{
if (rootFrame.Content() == nullptr)
{
// When the navigation stack isn't restored navigate to the first page,
// configuring the new page by passing required information as a navigation
// parameter
rootFrame.Navigate(xaml_typename(), box_value(e.Arguments()));
}
// Ensure the current window is active
Window::Current().Activate();
}
}
}
///
/// Invoked when application execution is being suspended. Application state is saved
/// without knowing whether the application will be terminated or resumed with the contents
/// of memory still intact.
///
/// The source of the suspend request.
/// Details about the suspend request.
void App::OnSuspending([[maybe_unused]] IInspectable const& sender, [[maybe_unused]] SuspendingEventArgs const& e)
{
// Save application state and stop any background activity
}
///
/// Invoked when Navigation to a certain page fails
///
/// The Frame which failed navigation
/// Details about the navigation failure
void App::OnNavigationFailed(IInspectable const&, NavigationFailedEventArgs const& e)
{
throw hresult_error(E_FAIL, hstring(L"Failed to load Page ") + e.SourcePageType().Name);
}
================================================
FILE: Examples/CodePushDemoAppCpp/windows/CodePushDemoAppCpp/App.h
================================================
#pragma once
#include "App.xaml.g.h"
#include "winrt/Microsoft.ReactNative.h"
namespace activation = winrt::Windows::ApplicationModel::Activation;
namespace winrt::CodePushDemoAppCpp::implementation
{
struct App : AppT
{
App() noexcept;
void OnLaunched(Windows::ApplicationModel::Activation::LaunchActivatedEventArgs const&);
void OnSuspending(IInspectable const&, Windows::ApplicationModel::SuspendingEventArgs const&);
void OnNavigationFailed(IInspectable const&, Windows::UI::Xaml::Navigation::NavigationFailedEventArgs const&);
winrt::Microsoft::ReactNative::ReactNativeHost& Host() noexcept { return m_host; }
private:
winrt::Microsoft::ReactNative::ReactNativeHost m_host;
};
} // namespace winrt::CodePushDemoAppCpp::implementation
================================================
FILE: Examples/CodePushDemoAppCpp/windows/CodePushDemoAppCpp/App.idl
================================================
namespace CodePushDemoAppCpp
{
}
================================================
FILE: Examples/CodePushDemoAppCpp/windows/CodePushDemoAppCpp/App.xaml
================================================
================================================
FILE: Examples/CodePushDemoAppCpp/windows/CodePushDemoAppCpp/AutolinkedNativeModules.g.cpp
================================================
// AutolinkedNativeModules.g.cpp contents generated by "react-native autolink-windows"
// clang-format off
#include "pch.h"
#include "AutolinkedNativeModules.g.h"
// Includes from react-native-code-push-windows
#include "winrt/Microsoft.CodePush.ReactNative.h"
namespace winrt::Microsoft::ReactNative
{
void RegisterAutolinkedNativeModulePackages(winrt::Windows::Foundation::Collections::IVector const& packageProviders)
{
// IReactPackageProviders from react-native-code-push-windows
packageProviders.Append(winrt::Microsoft::CodePush::ReactNative::ReactPackageProvider());
}
}
================================================
FILE: Examples/CodePushDemoAppCpp/windows/CodePushDemoAppCpp/AutolinkedNativeModules.g.h
================================================
// AutolinkedNativeModules.g.h contents generated by "react-native autolink-windows"
#pragma once
namespace winrt::Microsoft::ReactNative
{
void RegisterAutolinkedNativeModulePackages(winrt::Windows::Foundation::Collections::IVector const& packageProviders);
}
================================================
FILE: Examples/CodePushDemoAppCpp/windows/CodePushDemoAppCpp/AutolinkedNativeModules.g.targets
================================================
================================================
FILE: Examples/CodePushDemoAppCpp/windows/CodePushDemoAppCpp/CodePushDemoAppCpp.vcxproj
================================================
true
true
true
{609f44f6-01cf-4270-85a3-09f9ca10b434}
CodePushDemoAppCpp
CodePushDemoAppCpp
en-US
16.0
true
Windows Store
10.0
10.0.18362.0
10.0.16299.0
CodePushDemoAppCpp_TemporaryKey.pfx
5FBADC5BFA887CF7B571A1685542D69E23F6A0F2
password
$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), 'node_modules\react-native-windows\package.json'))\node_modules\react-native-windows\
true
false
Debug
ARM
Debug
ARM64
Debug
Win32
Debug
x64
Release
ARM
Release
ARM64
Release
Win32
Release
x64
Application
Unicode
true
true
false
true
false
Use
pch.h
$(IntDir)pch.pch
Level4
%(AdditionalOptions) /bigobj
4453;28204
_DEBUG;%(PreprocessorDefinitions)
NDEBUG;%(PreprocessorDefinitions)
MainPage.xaml
Code
App.xaml
Designer
Designer
MainPage.xaml
Code
Create
App.xaml
App.xaml
MainPage.xaml
Code
Designer
{a6b6216e-fa3f-45e2-9c8e-40023cce9132}
This project references targets in your node_modules\react-native-windows folder. The missing file is {0}.
This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.
================================================
FILE: Examples/CodePushDemoAppCpp/windows/CodePushDemoAppCpp/CodePushDemoAppCpp.vcxproj.filters
================================================
Assets
Assets
Assets
Assets
Assets
Assets
Assets
{e48dc53e-40b1-40cb-970a-f89935452892}
================================================
FILE: Examples/CodePushDemoAppCpp/windows/CodePushDemoAppCpp/MainPage.cpp
================================================
#include "pch.h"
#include "MainPage.h"
#if __has_include("MainPage.g.cpp")
#include "MainPage.g.cpp"
#endif
#include "App.h"
using namespace winrt;
using namespace Windows::UI::Xaml;
namespace winrt::CodePushDemoAppCpp::implementation
{
MainPage::MainPage()
{
InitializeComponent();
auto app = Application::Current().as();
ReactRootView().ReactNativeHost(app->Host());
}
}
================================================
FILE: Examples/CodePushDemoAppCpp/windows/CodePushDemoAppCpp/MainPage.h
================================================
#pragma once
#include "MainPage.g.h"
#include
namespace winrt::CodePushDemoAppCpp::implementation
{
struct MainPage : MainPageT
{
MainPage();
};
}
namespace winrt::CodePushDemoAppCpp::factory_implementation
{
struct MainPage : MainPageT
{
};
}
================================================
FILE: Examples/CodePushDemoAppCpp/windows/CodePushDemoAppCpp/MainPage.idl
================================================
namespace CodePushDemoAppCpp
{
[default_interface]
runtimeclass MainPage : Windows.UI.Xaml.Controls.Page
{
MainPage();
}
}
================================================
FILE: Examples/CodePushDemoAppCpp/windows/CodePushDemoAppCpp/MainPage.xaml
================================================
================================================
FILE: Examples/CodePushDemoAppCpp/windows/CodePushDemoAppCpp/Package.appxmanifest
================================================
CodePushDemoAppCpp
Aleksey
Assets\StoreLogo.png
================================================
FILE: Examples/CodePushDemoAppCpp/windows/CodePushDemoAppCpp/PropertySheet.props
================================================
================================================
FILE: Examples/CodePushDemoAppCpp/windows/CodePushDemoAppCpp/ReactPackageProvider.cpp
================================================
#include "pch.h"
#include "ReactPackageProvider.h"
#include "NativeModules.h"
using namespace winrt::Microsoft::ReactNative;
namespace winrt::CodePushDemoAppCpp::implementation
{
void ReactPackageProvider::CreatePackage(IReactPackageBuilder const &packageBuilder) noexcept
{
AddAttributedModules(packageBuilder);
}
} // namespace winrt::CodePushDemoAppCpp::implementation
================================================
FILE: Examples/CodePushDemoAppCpp/windows/CodePushDemoAppCpp/ReactPackageProvider.h
================================================
#pragma once
#include "winrt/Microsoft.ReactNative.h"
namespace winrt::CodePushDemoAppCpp::implementation
{
struct ReactPackageProvider : winrt::implements
{
public: // IReactPackageProvider
void CreatePackage(winrt::Microsoft::ReactNative::IReactPackageBuilder const &packageBuilder) noexcept;
};
} // namespace winrt::CodePushDemoAppCpp::implementation
================================================
FILE: Examples/CodePushDemoAppCpp/windows/CodePushDemoAppCpp/packages.config
================================================
================================================
FILE: Examples/CodePushDemoAppCpp/windows/CodePushDemoAppCpp/pch.cpp
================================================
#include "pch.h"
================================================
FILE: Examples/CodePushDemoAppCpp/windows/CodePushDemoAppCpp/pch.h
================================================
#pragma once
#define NOMINMAX
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
================================================
FILE: Examples/CodePushDemoAppCpp/windows/CodePushDemoAppCpp.sln
================================================
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.29215.179
MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CodePushDemoAppCpp", "CodePushDemoAppCpp\CodePushDemoAppCpp.vcxproj", "{609F44F6-01CF-4270-85A3-09F9CA10B434}"
ProjectSection(ProjectDependencies) = postProject
{F7D32BD0-2749-483E-9A0D-1635EF7E3136} = {F7D32BD0-2749-483E-9A0D-1635EF7E3136}
EndProjectSection
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Folly", "..\node_modules\react-native-windows\Folly\Folly.vcxproj", "{A990658C-CE31-4BCC-976F-0FC6B1AF693D}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ReactCommon", "..\node_modules\react-native-windows\ReactCommon\ReactCommon.vcxproj", "{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}"
ProjectSection(ProjectDependencies) = postProject
{A990658C-CE31-4BCC-976F-0FC6B1AF693D} = {A990658C-CE31-4BCC-976F-0FC6B1AF693D}
EndProjectSection
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Chakra", "..\node_modules\react-native-windows\Chakra\Chakra.vcxitems", "{C38970C0-5FBF-4D69-90D8-CBAC225AE895}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Microsoft.ReactNative", "..\node_modules\react-native-windows\Microsoft.ReactNative\Microsoft.ReactNative.vcxproj", "{F7D32BD0-2749-483E-9A0D-1635EF7E3136}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "JSI.Shared", "..\node_modules\react-native-windows\JSI\Shared\JSI.Shared.vcxitems", "{0CC28589-39E4-4288-B162-97B959F8B843}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "JSI.Universal", "..\node_modules\react-native-windows\JSI\Universal\JSI.Universal.vcxproj", "{A62D504A-16B8-41D2-9F19-E2E86019E5E4}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Microsoft.ReactNative.Cxx", "..\node_modules\react-native-windows\Microsoft.ReactNative.Cxx\Microsoft.ReactNative.Cxx.vcxitems", "{DA8B35B3-DA00-4B02-BDE6-6A397B3FD46B}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Common", "..\node_modules\react-native-windows\Common\Common.vcxproj", "{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ReactNative", "ReactNative", "{5EA20F54-880A-49F3-99FA-4B3FE54E8AB1}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Microsoft.ReactNative.Shared", "..\node_modules\react-native-windows\Shared\Shared.vcxitems", "{2049DBE9-8D13-42C9-AE4B-413AE38FFFD0}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Mso", "..\node_modules\react-native-windows\Mso\Mso.vcxitems", "{84E05BFA-CBAF-4F0D-BFB6-4CE85742A57E}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Include", "..\node_modules\react-native-windows\include\Include.vcxitems", "{EF074BA1-2D54-4D49-A28E-5E040B47CD2E}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CodePush", "..\..\..\windows\CodePush\CodePush.vcxproj", "{A6B6216E-FA3F-45E2-9C8E-40023CCE9132}"
EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
..\node_modules\react-native-windows\JSI\Shared\JSI.Shared.vcxitems*{0cc28589-39e4-4288-b162-97b959f8b843}*SharedItemsImports = 9
..\node_modules\react-native-windows\Shared\Shared.vcxitems*{2049dbe9-8d13-42c9-ae4b-413ae38fffd0}*SharedItemsImports = 9
..\node_modules\react-native-windows\Mso\Mso.vcxitems*{84e05bfa-cbaf-4f0d-bfb6-4ce85742a57e}*SharedItemsImports = 9
..\node_modules\react-native-windows\JSI\Shared\JSI.Shared.vcxitems*{a62d504a-16b8-41d2-9f19-e2e86019e5e4}*SharedItemsImports = 4
..\node_modules\react-native-windows\Chakra\Chakra.vcxitems*{c38970c0-5fbf-4d69-90d8-cbac225ae895}*SharedItemsImports = 9
..\node_modules\react-native-windows\Microsoft.ReactNative.Cxx\Microsoft.ReactNative.Cxx.vcxitems*{da8b35b3-da00-4b02-bde6-6a397b3fd46b}*SharedItemsImports = 9
..\node_modules\react-native-windows\include\Include.vcxitems*{ef074ba1-2d54-4d49-a28e-5e040b47cd2e}*SharedItemsImports = 9
..\node_modules\react-native-windows\Chakra\Chakra.vcxitems*{f7d32bd0-2749-483e-9a0d-1635ef7e3136}*SharedItemsImports = 4
..\node_modules\react-native-windows\Microsoft.ReactNative.Cxx\Microsoft.ReactNative.Cxx.vcxitems*{f7d32bd0-2749-483e-9a0d-1635ef7e3136}*SharedItemsImports = 4
..\node_modules\react-native-windows\Mso\Mso.vcxitems*{f7d32bd0-2749-483e-9a0d-1635ef7e3136}*SharedItemsImports = 4
..\node_modules\react-native-windows\Shared\Shared.vcxitems*{f7d32bd0-2749-483e-9a0d-1635ef7e3136}*SharedItemsImports = 4
EndGlobalSection
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|ARM = Debug|ARM
Debug|ARM64 = Debug|ARM64
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|ARM = Release|ARM
Release|ARM64 = Release|ARM64
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{609F44F6-01CF-4270-85A3-09F9CA10B434}.Debug|ARM.ActiveCfg = Debug|ARM
{609F44F6-01CF-4270-85A3-09F9CA10B434}.Debug|ARM.Build.0 = Debug|ARM
{609F44F6-01CF-4270-85A3-09F9CA10B434}.Debug|ARM.Deploy.0 = Debug|ARM
{609F44F6-01CF-4270-85A3-09F9CA10B434}.Debug|ARM64.ActiveCfg = Debug|ARM64
{609F44F6-01CF-4270-85A3-09F9CA10B434}.Debug|ARM64.Build.0 = Debug|ARM64
{609F44F6-01CF-4270-85A3-09F9CA10B434}.Debug|ARM64.Deploy.0 = Debug|ARM64
{609F44F6-01CF-4270-85A3-09F9CA10B434}.Debug|x64.ActiveCfg = Debug|x64
{609F44F6-01CF-4270-85A3-09F9CA10B434}.Debug|x64.Build.0 = Debug|x64
{609F44F6-01CF-4270-85A3-09F9CA10B434}.Debug|x64.Deploy.0 = Debug|x64
{609F44F6-01CF-4270-85A3-09F9CA10B434}.Debug|x86.ActiveCfg = Debug|Win32
{609F44F6-01CF-4270-85A3-09F9CA10B434}.Debug|x86.Build.0 = Debug|Win32
{609F44F6-01CF-4270-85A3-09F9CA10B434}.Debug|x86.Deploy.0 = Debug|Win32
{609F44F6-01CF-4270-85A3-09F9CA10B434}.Release|ARM.ActiveCfg = Release|ARM
{609F44F6-01CF-4270-85A3-09F9CA10B434}.Release|ARM.Build.0 = Release|ARM
{609F44F6-01CF-4270-85A3-09F9CA10B434}.Release|ARM.Deploy.0 = Release|ARM
{609F44F6-01CF-4270-85A3-09F9CA10B434}.Release|ARM64.ActiveCfg = Release|ARM64
{609F44F6-01CF-4270-85A3-09F9CA10B434}.Release|ARM64.Build.0 = Release|ARM64
{609F44F6-01CF-4270-85A3-09F9CA10B434}.Release|ARM64.Deploy.0 = Release|ARM64
{609F44F6-01CF-4270-85A3-09F9CA10B434}.Release|x64.ActiveCfg = Release|x64
{609F44F6-01CF-4270-85A3-09F9CA10B434}.Release|x64.Build.0 = Release|x64
{609F44F6-01CF-4270-85A3-09F9CA10B434}.Release|x64.Deploy.0 = Release|x64
{609F44F6-01CF-4270-85A3-09F9CA10B434}.Release|x86.ActiveCfg = Release|Win32
{609F44F6-01CF-4270-85A3-09F9CA10B434}.Release|x86.Build.0 = Release|Win32
{609F44F6-01CF-4270-85A3-09F9CA10B434}.Release|x86.Deploy.0 = Release|Win32
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Debug|ARM.ActiveCfg = Debug|ARM
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Debug|ARM.Build.0 = Debug|ARM
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Debug|ARM64.ActiveCfg = Debug|ARM64
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Debug|ARM64.Build.0 = Debug|ARM64
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Debug|x64.ActiveCfg = Debug|x64
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Debug|x64.Build.0 = Debug|x64
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Debug|x86.ActiveCfg = Debug|Win32
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Debug|x86.Build.0 = Debug|Win32
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Release|ARM.ActiveCfg = Release|ARM
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Release|ARM.Build.0 = Release|ARM
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Release|ARM64.ActiveCfg = Release|ARM64
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Release|ARM64.Build.0 = Release|ARM64
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Release|x64.ActiveCfg = Release|x64
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Release|x64.Build.0 = Release|x64
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Release|x86.ActiveCfg = Release|Win32
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Release|x86.Build.0 = Release|Win32
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Debug|ARM.ActiveCfg = Debug|ARM
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Debug|ARM.Build.0 = Debug|ARM
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Debug|ARM64.ActiveCfg = Debug|ARM64
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Debug|ARM64.Build.0 = Debug|ARM64
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Debug|x64.ActiveCfg = Debug|x64
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Debug|x64.Build.0 = Debug|x64
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Debug|x86.ActiveCfg = Debug|Win32
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Debug|x86.Build.0 = Debug|Win32
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Release|ARM.ActiveCfg = Release|ARM
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Release|ARM.Build.0 = Release|ARM
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Release|ARM64.ActiveCfg = Release|ARM64
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Release|ARM64.Build.0 = Release|ARM64
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Release|x64.ActiveCfg = Release|x64
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Release|x64.Build.0 = Release|x64
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Release|x86.ActiveCfg = Release|Win32
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Release|x86.Build.0 = Release|Win32
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Debug|ARM.ActiveCfg = Debug|ARM
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Debug|ARM.Build.0 = Debug|ARM
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Debug|ARM64.ActiveCfg = Debug|ARM64
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Debug|ARM64.Build.0 = Debug|ARM64
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Debug|x64.ActiveCfg = Debug|x64
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Debug|x64.Build.0 = Debug|x64
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Debug|x86.ActiveCfg = Debug|Win32
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Debug|x86.Build.0 = Debug|Win32
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Release|ARM.ActiveCfg = Release|ARM
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Release|ARM.Build.0 = Release|ARM
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Release|ARM64.ActiveCfg = Release|ARM64
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Release|ARM64.Build.0 = Release|ARM64
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Release|x64.ActiveCfg = Release|x64
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Release|x64.Build.0 = Release|x64
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Release|x86.ActiveCfg = Release|Win32
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Release|x86.Build.0 = Release|Win32
{A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Debug|ARM.ActiveCfg = Debug|ARM
{A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Debug|ARM.Build.0 = Debug|ARM
{A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Debug|ARM64.ActiveCfg = Debug|ARM64
{A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Debug|ARM64.Build.0 = Debug|ARM64
{A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Debug|x64.ActiveCfg = Debug|x64
{A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Debug|x64.Build.0 = Debug|x64
{A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Debug|x86.ActiveCfg = Debug|Win32
{A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Debug|x86.Build.0 = Debug|Win32
{A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Release|ARM.ActiveCfg = Release|ARM
{A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Release|ARM.Build.0 = Release|ARM
{A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Release|ARM64.ActiveCfg = Release|ARM64
{A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Release|ARM64.Build.0 = Release|ARM64
{A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Release|x64.ActiveCfg = Release|x64
{A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Release|x64.Build.0 = Release|x64
{A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Release|x86.ActiveCfg = Release|Win32
{A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Release|x86.Build.0 = Release|Win32
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Debug|ARM.ActiveCfg = Debug|ARM
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Debug|ARM.Build.0 = Debug|ARM
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Debug|ARM64.ActiveCfg = Debug|ARM64
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Debug|ARM64.Build.0 = Debug|ARM64
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Debug|x64.ActiveCfg = Debug|x64
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Debug|x64.Build.0 = Debug|x64
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Debug|x86.ActiveCfg = Debug|Win32
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Debug|x86.Build.0 = Debug|Win32
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Release|ARM.ActiveCfg = Release|ARM
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Release|ARM.Build.0 = Release|ARM
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Release|ARM64.ActiveCfg = Release|ARM64
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Release|ARM64.Build.0 = Release|ARM64
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Release|x64.ActiveCfg = Release|x64
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Release|x64.Build.0 = Release|x64
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Release|x86.ActiveCfg = Release|Win32
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Release|x86.Build.0 = Release|Win32
{A6B6216E-FA3F-45E2-9C8E-40023CCE9132}.Debug|ARM.ActiveCfg = Debug|ARM
{A6B6216E-FA3F-45E2-9C8E-40023CCE9132}.Debug|ARM.Build.0 = Debug|ARM
{A6B6216E-FA3F-45E2-9C8E-40023CCE9132}.Debug|ARM64.ActiveCfg = Debug|ARM64
{A6B6216E-FA3F-45E2-9C8E-40023CCE9132}.Debug|ARM64.Build.0 = Debug|ARM64
{A6B6216E-FA3F-45E2-9C8E-40023CCE9132}.Debug|x64.ActiveCfg = Debug|x64
{A6B6216E-FA3F-45E2-9C8E-40023CCE9132}.Debug|x64.Build.0 = Debug|x64
{A6B6216E-FA3F-45E2-9C8E-40023CCE9132}.Debug|x86.ActiveCfg = Debug|Win32
{A6B6216E-FA3F-45E2-9C8E-40023CCE9132}.Debug|x86.Build.0 = Debug|Win32
{A6B6216E-FA3F-45E2-9C8E-40023CCE9132}.Release|ARM.ActiveCfg = Release|ARM
{A6B6216E-FA3F-45E2-9C8E-40023CCE9132}.Release|ARM.Build.0 = Release|ARM
{A6B6216E-FA3F-45E2-9C8E-40023CCE9132}.Release|ARM64.ActiveCfg = Release|ARM64
{A6B6216E-FA3F-45E2-9C8E-40023CCE9132}.Release|ARM64.Build.0 = Release|ARM64
{A6B6216E-FA3F-45E2-9C8E-40023CCE9132}.Release|x64.ActiveCfg = Release|x64
{A6B6216E-FA3F-45E2-9C8E-40023CCE9132}.Release|x64.Build.0 = Release|x64
{A6B6216E-FA3F-45E2-9C8E-40023CCE9132}.Release|x86.ActiveCfg = Release|Win32
{A6B6216E-FA3F-45E2-9C8E-40023CCE9132}.Release|x86.Build.0 = Release|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{A990658C-CE31-4BCC-976F-0FC6B1AF693D} = {5EA20F54-880A-49F3-99FA-4B3FE54E8AB1}
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD} = {5EA20F54-880A-49F3-99FA-4B3FE54E8AB1}
{C38970C0-5FBF-4D69-90D8-CBAC225AE895} = {5EA20F54-880A-49F3-99FA-4B3FE54E8AB1}
{F7D32BD0-2749-483E-9A0D-1635EF7E3136} = {5EA20F54-880A-49F3-99FA-4B3FE54E8AB1}
{0CC28589-39E4-4288-B162-97B959F8B843} = {5EA20F54-880A-49F3-99FA-4B3FE54E8AB1}
{A62D504A-16B8-41D2-9F19-E2E86019E5E4} = {5EA20F54-880A-49F3-99FA-4B3FE54E8AB1}
{DA8B35B3-DA00-4B02-BDE6-6A397B3FD46B} = {5EA20F54-880A-49F3-99FA-4B3FE54E8AB1}
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D} = {5EA20F54-880A-49F3-99FA-4B3FE54E8AB1}
{2049DBE9-8D13-42C9-AE4B-413AE38FFFD0} = {5EA20F54-880A-49F3-99FA-4B3FE54E8AB1}
{84E05BFA-CBAF-4F0D-BFB6-4CE85742A57E} = {5EA20F54-880A-49F3-99FA-4B3FE54E8AB1}
{EF074BA1-2D54-4D49-A28E-5E040B47CD2E} = {5EA20F54-880A-49F3-99FA-4B3FE54E8AB1}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {D43FAD39-F619-437D-BB40-04A3982ACB6A}
EndGlobalSection
EndGlobal
================================================
FILE: Examples/create-app.js
================================================
/*
The script serves to generate CodePushified React Native app to reproduce issues or for testing purposes.
NOTE: Use CodePushDemoApp and CodePushDemoAppCpp as reference how to implement CodePush in your app. For creating a working demo app
follow steps bellow.
Requirements:
1. npm i -g react-native-cli
2. npm i -g appcenter-cli
3. appcenter login
4. If you use this script on macOS for react-native v0.60+ then you need to have CocoaPods installed. Use this command to (re)install CocoaPods:
sudo gem install cocoapods -n /usr/local/bin
Usage: node create-app.js
1. node create-app.js
2. node create-app.js myapp
3. node create-app.js myapp react-native@0.62 react-native-code-push@6.1.0
4. node create-app.js myapp react-native@latest Microsoft/react-native-code-push
Parameters:
1. - CodePushDemoAppTest
2. - react-native@latest
3. - react-native-code-push@latest
*/
const fs = require('fs');
const path = require('path');
const nexpect = require('./nexpect');
const child_process = require('child_process');
const execSync = child_process.execSync;
const args = process.argv.slice(2);
const appName = args[0] || 'CodePushDemoAppTest';
if (fs.existsSync(appName)) {
console.error(`Folder with name "${appName}" already exists! Please delete`);
process.exit();
}
const appNameAndroid = `${appName}-android`;
const appNameIOS = `${appName}-ios`;
let owner = null;
const reactNativeVersion = args[1] || `react-native@${execCommand('npm view react-native version')}`.trim();
const reactNativeVersionIsLowerThanV049 = isReactNativeVersionLowerThan(49);
const reactNativeCodePushVersion = args[2] || `react-native-code-push@${execCommand('npm view react-native-code-push version')}`.trim();
const reactNativeVersionIsLowerThanV073 = isReactNativeVersionLowerThan(73)
if (!isReactNativeVersionLowerThan(60) && process.platform === "darwin") {
try {
console.log("Verify that CocoaPods installed");
execCommand("pod --version");
console.log("CocoaPods has installed");
} catch {
console.error(`'CocoaPods' are required to run the script, you can install it with\n'sudo gem install cocoapods -n /usr/local/bin'\ncommand`);
process.exit();
}
}
console.log(`App name: ${appName}`);
console.log(`React Native version: ${reactNativeVersion}`);
console.log(`React Native Module for CodePush version: ${reactNativeCodePushVersion} \n`);
let androidStagingDeploymentKey = null;
let iosStagingDeploymentKey = null;
//GENERATE START
createCodePushApp(appNameAndroid, 'Android');
createCodePushApp(appNameIOS, 'iOS');
generatePlainReactNativeApp(appName, reactNativeVersion);
process.chdir(appName);
installCodePush(reactNativeCodePushVersion);
linkCodePush(androidStagingDeploymentKey, iosStagingDeploymentKey);
//GENERATE END
function createCodePushApp(name, os) {
try {
console.log(`Creating CodePush app "${name}" to release updates for ${os}...`);
try {
const appResult = execCommand(`appcenter apps create -d ${name} -n ${name} -o ${os} -p React-Native --output json`);
const app = JSON.parse(appResult);
owner = app.owner.name;
console.log(`App "${name}" has been created \n`);
} catch (e) {
console.error(`Error: Unable to create CodePush app. Please check that you haven't application with "${name}" name on portal.`,);
console.error("Error: ", e.toString());
}
execCommand(`appcenter codepush deployment add -a ${owner}/${name} Staging`);
} catch (e) {
console.error("Error", e.toString());
}
try {
const deploymentKeysResult = execCommand(`appcenter codepush deployment list -a ${owner}/${name} -k --output json`);
const deploymentKeys = JSON.parse(deploymentKeysResult);
const stagingDeploymentKey = deploymentKeys[0][1];
console.log(`Deployment key for ${os}: ${stagingDeploymentKey}`);
console.log(`Use "appcenter codepush release-react ${owner}/${name}" command to release updates for ${os} \n`);
switch (os) {
case 'Android':
androidStagingDeploymentKey = stagingDeploymentKey;
break;
case 'iOS':
iosStagingDeploymentKey = stagingDeploymentKey;
break;
}
} catch (e) {
console.error("Error: Unable to load deployment keys");
console.error("Error: ", e.toString());
}
}
function generatePlainReactNativeApp(appName, reactNativeVersion) {
console.log(`Installing React Native...`);
execCommand(`react-native init ${appName} --version ${reactNativeVersion.split('@')[1]}`);
console.log(`React Native has been installed \n`);
}
function installCodePush(reactNativeCodePushVersion) {
console.log(`Installing React Native Module for CodePush...`);
execCommand(`npm i ${reactNativeCodePushVersion}`);
console.log(`React Native Module for CodePush has been installed \n`);
}
function linkCodePush(androidStagingDeploymentKey, iosStagingDeploymentKey) {
console.log(`Linking React Native Module for CodePush...`);
if (isReactNativeVersionLowerThan(60)) {
nexpect.spawn(`react-native link react-native-code-push`)
.wait("What is your CodePush deployment key for Android (hit to ignore)")
.sendline(androidStagingDeploymentKey)
.wait("What is your CodePush deployment key for iOS (hit to ignore)")
.sendline(iosStagingDeploymentKey)
.run(function (err) {
if (!err) {
console.log(`React Native Module for CodePush has been linked \n`);
setupAssets();
}
else {
console.log(err);
}
});
} else {
androidSetup();
if (process.platform === 'darwin') {
iosSetup();
} else {
console.log('Your OS is not "Mac OS" so the iOS application will not be configured')
}
setupAssets();
console.log(`React Native Module for CodePush has been linked \n`);
}
}
function setupAssets() {
let fileToEdit;
if (reactNativeVersionIsLowerThanV049) {
fs.unlinkSync('./index.ios.js');
fs.unlinkSync('./index.android.js');
fs.writeFileSync('demo.js', fs.readFileSync('../CodePushDemoApp-pre0.49/demo.js'));
fs.writeFileSync('index.ios.js', fs.readFileSync('../CodePushDemoApp-pre0.49/index.ios.js'));
fs.writeFileSync('index.android.js', fs.readFileSync('../CodePushDemoApp-pre0.49/index.android.js'));
fileToEdit = 'demo.js'
} else {
fs.writeFileSync('index.js', fs.readFileSync(__dirname + '/CodePushDemoApp/index.js'));
fs.writeFileSync('App.js', fs.readFileSync(__dirname + '/CodePushDemoApp/App.js'));
fileToEdit = 'index.js'
}
copyRecursiveSync(__dirname + '/CodePushDemoApp/images', './images');
fs.readFile(fileToEdit, 'utf8', function (err, data) {
if (err) {
return console.error(err);
}
const result = data.replace(/CodePushDemoApp/g, appName);
fs.writeFile(fileToEdit, result, 'utf8', function (err) {
if (err) return console.error(err);
if (!/^win/.test(process.platform)) {
optimizeToTestInDebugMode();
process.chdir('../');
grantAccess(appName);
}
console.log(`\nReact Native app "${appName}" has been generated and CodePushified!`);
process.exit();
});
});
}
function optimizeToTestInDebugMode() {
const rnXcodeShLocationFolder = 'scripts';
try {
const rnVersions = JSON.parse(execCommand(`npm view react-native versions --json`));
const currentRNversion = JSON.parse(fs.readFileSync('./package.json'))['dependencies']['react-native'];
if (rnVersions.indexOf(currentRNversion) > -1 &&
rnVersions.indexOf(currentRNversion) < rnVersions.indexOf("0.46.0-rc.0")) {
rnXcodeShLocationFolder = 'packager';
}
} catch (e) { }
const rnXcodeShPath = `node_modules/react-native/${rnXcodeShLocationFolder}/react-native-xcode.sh`;
// Replace "if [[ "$PLATFORM_NAME" == *simulator ]]; then" with "if false; then" to force bundling
execCommand(`sed -ie 's/if \\[\\[ "\$PLATFORM_NAME" == \\*simulator \\]\\]; then/if false; then/' ${rnXcodeShPath}`);
execCommand(`perl -i -p0e 's/#ifdef DEBUG.*?#endif/jsCodeLocation = [CodePush bundleURL];/s' ios/${appName}/${getAppDelegateName()}`);
reactNativeVersionIsLowerThanV073 && execCommand(`sed -ie 's/targetName.toLowerCase().contains("release")/true/' node_modules/react-native/react.gradle`);
}
function grantAccess(folderPath) {
execCommand('chown -R `whoami` ' + folderPath);
}
function copyRecursiveSync(src, dest) {
const exists = fs.existsSync(src);
const stats = exists && fs.statSync(src);
const isDirectory = exists && stats.isDirectory();
if (exists && isDirectory) {
fs.mkdirSync(dest);
fs.readdirSync(src).forEach(function (childItemName) {
copyRecursiveSync(path.join(src, childItemName),
path.join(dest, childItemName));
});
} else {
fs.linkSync(src, dest);
}
}
function isReactNativeVersionLowerThan(version) {
if (!reactNativeVersion ||
reactNativeVersion == "react-native@latest" ||
reactNativeVersion == "react-native@next")
return false;
const reactNativeVersionNumberString = reactNativeVersion.split("@")[1];
return reactNativeVersionNumberString.split('.')[1] < version;
}
// Configuring android applications for react-native version higher than 0.60
function androidSetup() {
const buildGradlePath = path.join('android', 'app', 'build.gradle');
const settingsGradlePath = path.join('android', 'settings.gradle');
const mainApplicationType = reactNativeVersionIsLowerThanV073 ? 'java' : 'kt';
const mainApplicationPath = path.join('android', 'app', 'src', 'main', 'java', 'com', appName, `MainApplication.${mainApplicationType}`);
const stringsResourcesPath = path.join('android', 'app', 'src', 'main', 'res', 'values', 'strings.xml');
let stringsResourcesContent = fs.readFileSync(stringsResourcesPath, "utf8");
const insertAfterString = "";
const deploymentKeyString = `\t${androidStagingDeploymentKey || "deployment-key-here"}`;
stringsResourcesContent = stringsResourcesContent.replace(insertAfterString, `${insertAfterString}\n${deploymentKeyString}`);
fs.writeFileSync(stringsResourcesPath, stringsResourcesContent);
let buildGradleContents = fs.readFileSync(buildGradlePath, "utf8");
const reactGradleLink = buildGradleContents.match(/\napply from: ["'].*?react\.gradle["']/);
const codePushGradleLink = `\napply from: "../../node_modules/react-native-code-push/android/codepush.gradle"`;
if (reactGradleLink != null) {
buildGradleContents = buildGradleContents.replace(reactGradleLink[0],
`${reactGradleLink[0]}${codePushGradleLink}`);
fs.writeFileSync(buildGradlePath, buildGradleContents);
}
// react.gradle script removed from 0.71 thus this workaround
else {
const appPluginLastLine = buildGradleContents.match(/apply plugin: "com.facebook.react"/)[0];
buildGradleContents = buildGradleContents.replace(appPluginLastLine, `${appPluginLastLine}${codePushGradleLink}`)
fs.writeFileSync(buildGradlePath, buildGradleContents);
}
let settingsGradleContents = fs.readFileSync(settingsGradlePath, "utf8");
const settingsGradleInclude = "include \':app\'";
const codePushProjectImport = `':react-native-code-push'\nproject(':react-native-code-push').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-code-push/android/app')`;
settingsGradleContents = settingsGradleContents.replace(settingsGradleInclude,
`${settingsGradleInclude}, ${codePushProjectImport}`);
fs.writeFileSync(settingsGradlePath, settingsGradleContents);
let importCodePush = `\nimport com.microsoft.codepush.react.CodePush;`;
let reactNativeHostInstantiationImport = "import android.app.Application;";
let mainApplicationContents = fs.readFileSync(mainApplicationPath, "utf8");
let getJSBundleFileOverride = "";
let reactNativeHostInstantiation = "";
// handle react-native version with java
if (reactNativeVersionIsLowerThanV073) {
reactNativeHostInstantiation = "new ReactNativeHost(this) {";
getJSBundleFileOverride = `
@Override
protected String getJSBundleFile(){
return CodePush.getJSBundleFile();
}
`;
}
// handle react-native version with kotlin
else {
reactNativeHostInstantiation = "object : DefaultReactNativeHost(this) {"
getJSBundleFileOverride = `
override fun getJSBundleFile(): String {
return CodePush.getJSBundleFile()
}
`;
importCodePush = importCodePush.replace(';', '');
reactNativeHostInstantiationImport = reactNativeHostInstantiationImport.replace(';', '');
}
mainApplicationContents = mainApplicationContents.replace(reactNativeHostInstantiation,
`${reactNativeHostInstantiation}${getJSBundleFileOverride}`);
mainApplicationContents = mainApplicationContents.replace(reactNativeHostInstantiationImport,
`${reactNativeHostInstantiationImport}${importCodePush}`);
fs.writeFileSync(mainApplicationPath, mainApplicationContents);
}
function getAppDelegateName() { return fs.readdirSync(path.join('ios', appName)).find(file => file === ('AppDelegate.mm') || file === 'AppDelegate.m') };
// Configuring ios applications for react-native version higher than 0.60
function iosSetup() {
const plistPath = path.join('ios', appName, 'Info.plist');
const appDelegatePath = path.join('ios', appName, getAppDelegateName());
let plistContents = fs.readFileSync(plistPath, "utf8");
const dictPlistTag = `\n`;
const codePushDeploymentKey = iosStagingDeploymentKey || 'deployment-key-here';
plistContents = plistContents.replace(dictPlistTag,
`\tCodePushDeploymentKey\n\t${codePushDeploymentKey}${dictPlistTag}`);
fs.writeFileSync(plistPath, plistContents);
let appDelegateContents = fs.readFileSync(appDelegatePath, "utf8");
const appDelegateHeaderImportStatement = `#import "AppDelegate.h"`;
const codePushHeaderImportStatementFormatted = `\n#import `;
appDelegateContents = appDelegateContents.replace(appDelegateHeaderImportStatement,
`${appDelegateHeaderImportStatement}${codePushHeaderImportStatementFormatted}`);
const oldBundleUrl = "[[NSBundle mainBundle] URLForResource:@\"main\" withExtension:@\"jsbundle\"]";
const codePushBundleUrl = "[CodePush bundleURL]";
appDelegateContents = appDelegateContents.replace(oldBundleUrl, codePushBundleUrl);
fs.writeFileSync(appDelegatePath, appDelegateContents);
execCommand(`cd ios && pod install && cd ..`);
}
function execCommand(command) {
console.log(`\n\x1b[2m${command}\x1b[0m\n`);
const result = execSync(command).toString();
return result;
}
================================================
FILE: Examples/nexpect.js
================================================
/*
* nexpect.js: Top-level include for the `nexpect` module.
*
* (C) 2011, Elijah Insua, Marak Squires, Charlie Robbins.
*
*/
var spawn = require('child_process').spawn;
var util = require('util');
var AssertionError = require('assert').AssertionError;
function chain (context) {
return {
expect: function (expectation) {
var _expect = function _expect (data) {
return testExpectation(data, expectation);
};
_expect.shift = true;
_expect.expectation = expectation;
_expect.description = '[expect] ' + expectation;
_expect.requiresInput = true;
context.queue.push(_expect);
return chain(context);
},
wait: function (expectation, callback) {
var _wait = function _wait (data) {
var val = testExpectation(data, expectation);
if (val === true && typeof callback === 'function') {
callback(data);
}
return val;
};
_wait.shift = false;
_wait.expectation = expectation;
_wait.description = '[wait] ' + expectation;
_wait.requiresInput = true;
context.queue.push(_wait);
return chain(context);
},
sendline: function (line) {
var _sendline = function _sendline () {
context.process.stdin.write(line + '\n');
if (context.verbose) {
process.stdout.write(line + '\n');
}
};
_sendline.shift = true;
_sendline.description = '[sendline] ' + line;
_sendline.requiresInput = false;
context.queue.push(_sendline);
return chain(context);
},
sendEof: function() {
var _sendEof = function _sendEof () {
context.process.stdin.destroy();
};
_sendEof.shift = true;
_sendEof.description = '[sendEof]';
_sendEof.requiresInput = false;
context.queue.push(_sendEof);
return chain(context);
},
run: function (callback) {
var errState = null,
responded = false,
stdout = [],
options;
//
// **onError**
//
// Helper function to respond to the callback with a
// specified error. Kills the child process if necessary.
//
function onError (err, kill) {
if (errState || responded) {
return;
}
errState = err;
responded = true;
if (kill) {
try { context.process.kill(); }
catch (ex) { }
}
callback(err);
}
//
// **validateFnType**
//
// Helper function to validate the `currentFn` in the
// `context.queue` for the target chain.
//
function validateFnType (currentFn) {
if (typeof currentFn !== 'function') {
//
// If the `currentFn` is not a function, short-circuit with an error.
//
onError(new Error('Cannot process non-function on nexpect stack.'), true);
return false;
}
else if (['_expect', '_sendline', '_wait', '_sendEof'].indexOf(currentFn.name) === -1) {
//
// If the `currentFn` is a function, but not those set by `.sendline()` or
// `.expect()` then short-circuit with an error.
//
onError(new Error('Unexpected context function name: ' + currentFn.name), true);
return false;
}
return true;
}
//
// **evalContext**
//
// Core evaluation logic that evaluates the next function in
// `context.queue` against the specified `data` where the last
// function run had `name`.
//
function evalContext (data, name) {
var currentFn = context.queue[0];
if (!currentFn || (name === '_expect' && currentFn.name === '_expect')) {
//
// If there is nothing left on the context or we are trying to
// evaluate two consecutive `_expect` functions, return.
//
return;
}
if (currentFn.shift) {
context.queue.shift();
}
if (!validateFnType(currentFn)) {
return;
}
if (currentFn.name === '_expect') {
//
// If this is an `_expect` function, then evaluate it and attempt
// to evaluate the next function (in case it is a `_sendline` function).
//
return currentFn(data) === true ?
evalContext(data, '_expect') :
onError(createExpectationError(currentFn.expectation, data), true);
}
else if (currentFn.name === '_wait') {
//
// If this is a `_wait` function, then evaluate it and if it returns true,
// then evaluate the function (in case it is a `_sendline` function).
//
if (currentFn(data) === true) {
context.queue.shift();
evalContext(data, '_expect');
}
}
else {
//
// If the `currentFn` is any other function then evaluate it
//
currentFn();
// Evaluate the next function if it does not need input
var nextFn = context.queue[0];
if (nextFn && !nextFn.requiresInput)
evalContext(data);
}
}
//
// **onLine**
//
// Preprocesses the `data` from `context.process` on the
// specified `context.stream` and then evaluates the processed lines:
//
// 1. Stripping ANSI colors (if necessary)
// 2. Removing case sensitivity (if necessary)
// 3. Splitting `data` into multiple lines.
//
function onLine (data) {
data = data.toString();
if (context.stripColors) {
data = data.replace(/\u001b\[\d{0,2}m/g, '');
}
if (context.ignoreCase) {
data = data.toLowerCase();
}
var lines = data.split('\n').filter(function (line) { return line.length > 0; });
stdout = stdout.concat(lines);
while (lines.length > 0) {
evalContext(lines.shift(), null);
}
}
//
// **flushQueue**
//
// Helper function which flushes any remaining functions from
// `context.queue` and responds to the `callback` accordingly.
//
function flushQueue () {
var remainingQueue = context.queue.slice(),
currentFn = context.queue.shift(),
lastLine = stdout[stdout.length - 1];
if (!lastLine) {
onError(createUnexpectedEndError(
'No data from child with non-empty queue.', remainingQueue));
return false;
}
else if (context.queue.length > 0) {
onError(createUnexpectedEndError(
'Non-empty queue on spawn exit.', remainingQueue));
return false;
}
else if (!validateFnType(currentFn)) {
// onError was called
return false;
}
else if (currentFn.name === '_sendline') {
onError(new Error('Cannot call sendline after the process has exited'));
return false;
}
else if (currentFn.name === '_wait' || currentFn.name === '_expect') {
if (currentFn(lastLine) !== true) {
onError(createExpectationError(currentFn.expectation, lastLine));
return false;
}
}
return true;
}
//
// **onData**
//
// Helper function for writing any data from a stream
// to `process.stdout`.
//
function onData (data) {
process.stdout.write(data);
}
options = {
cwd: context.cwd,
env: context.env
};
//
// Spawn the child process and begin processing the target
// stream for this chain.
//
if (!/^win/.test(process.platform)) {
context.process = spawn(context.command, context.params, options);
} else {
context.process = spawn('cmd', ['/c', `${context.command}`].concat(context.params), options);
}
if (context.verbose) {
context.process.stdout.on('data', onData);
context.process.stderr.on('data', onData);
}
if (context.stream === 'all') {
context.process.stdout.on('data', onLine);
context.process.stderr.on('data', onLine);
} else {
context.process[context.stream].on('data', onLine);
}
context.process.on('error', onError);
//
// When the process exits, check the output `code` and `signal`,
// flush `context.queue` (if necessary) and respond to the callback
// appropriately.
//
context.process.on('close', function (code, signal) {
if (code === 127) {
// XXX(sam) Not how node works (anymore?), 127 is what /bin/sh returns,
// but it appears node does not, or not in all conditions, blithely
// return 127 to user, it emits an 'error' from the child_process.
//
// If the response code is `127` then `context.command` was not found.
//
return onError(new Error('Command not found: ' + context.command));
}
else if (context.queue.length && !flushQueue()) {
// if flushQueue returned false, onError was called
return;
}
callback(null, stdout, signal || code);
});
return context.process;
}
};
}
function testExpectation(data, expectation) {
if (util.isRegExp(expectation)) {
return expectation.test(data);
} else {
return data.indexOf(expectation) > -1;
}
}
function createUnexpectedEndError(message, remainingQueue) {
var desc = remainingQueue.map(function(it) { return it.description; });
var msg = message + '\n' + desc.join('\n');
return new AssertionError({
message: msg,
expected: [],
actual: desc
});
}
function createExpectationError(expected, actual) {
var expectation;
if (util.isRegExp(expected))
expectation = 'to match ' + expected;
else
expectation = 'to contain ' + JSON.stringify(expected);
var err = new AssertionError({
message: util.format('expected %j %s', actual, expectation),
actual: actual,
expected: expected
});
return err;
}
function nspawn (command, params, options) {
if (arguments.length === 2) {
if (Array.isArray(arguments[1])) {
options = {};
}
else {
options = arguments[1];
params = null;
}
}
if (Array.isArray(command)) {
params = command;
command = params.shift();
}
else if (typeof command === 'string') {
command = command.split(' ');
params = params || command.slice(1);
command = command[0];
}
options = options || {};
context = {
command: command,
cwd: options.cwd || undefined,
env: options.env || undefined,
ignoreCase: options.ignoreCase,
params: params,
queue: [],
stream: options.stream || 'stdout',
stripColors: options.stripColors,
verbose: options.verbose
};
return chain(context);
}
//
// Export the core `nspawn` function as well as `nexpect.nspawn` for
// backwards compatibility.
//
module.exports.spawn = nspawn;
module.exports.nspawn = {
spawn: nspawn
};
================================================
FILE: LICENSE.md
================================================
Microsoft CodePush Plugin for React Native
Copyright (c) Microsoft Corporation
All rights reserved.
MIT License
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
================================================
FILE: README.md
================================================
# Archiving this repository
Visual Studio App Center was retired on March 31, 2025, except for its Analytics and Diagnostics features. You can learn more about the retirement and the Analytics and Diagnostics extension [here](https://aka.ms/appcenter/retire). CodePush, along with other App Center features, was also retired on March 31, 2025. Consequently, we are archiving this repository.
---
[](http://microsoft.github.io/code-push/)
#### [Sign up With App Center](https://appcenter.ms/signup?utm_source=CodePush&utm_medium=Azure) to use CodePush
## React Native Module for CodePush
> [!WARNING]
> React Native CodePush won't support new Architecture. In order to use this plugin on React Native versions starting from 0.76 you will need to [opt out](https://reactnative.dev/blog/2024/10/23/the-new-architecture-is-here#opt-out) from new architecture.
>
*Note: This README is only relevant to the latest version of our plugin. If you are using an older version, please switch to the relevant tag on [our GitHub repo](https://github.com/microsoft/react-native-code-push) to view the docs for that particular version.*

This plugin provides client-side integration for the [CodePush service](https://microsoft.github.io/code-push/), allowing you to easily add a dynamic update experience to your React Native app(s).
* [How does it work?](#how-does-it-work)
* [Supported React Native Platforms](#supported-react-native-platforms)
* [Supported Components](#supported-components)
* [Getting Started](#getting-started)
* [iOS Setup](docs/setup-ios.md)
* [Android Setup](docs/setup-android.md)
* [Windows Setup](docs/setup-windows.md)
* [Plugin Usage](#plugin-usage)
* [Store Guideline Compliance](#store-guideline-compliance)
* [Releasing Updates](#releasing-updates)
* [Multi-Deployment Testing](#multi-deployment-testing)
* [Android](docs/multi-deployment-testing-android.md)
* [iOS](docs/multi-deployment-testing-ios.md)
* [Dynamic Deployment Assignment](#dynamic-deployment-assignment)
* [API Reference](#api-reference)
* [JavaScript API](docs/api-js.md)
* [Objective-C API Reference (iOS)](docs/api-ios.md)
* [Java API Reference (Android)](docs/api-android.md)
* [Debugging / Troubleshooting](#debugging--troubleshooting)
* [Example Apps / Starters](#example-apps--starters)
* [Continuous Integration / Delivery](#continuous-integration--delivery)
* [TypeScript Consumption](#typescript-consumption)
## How does it work?
A React Native app is composed of JavaScript files and any accompanying [images](https://reactnative.dev/docs/image), which are bundled together by the [metro bundler](https://github.com/facebook/metro) and distributed as part of a platform-specific binary (i.e. an `.ipa` or `.apk` file). Once the app is released, updating either the JavaScript code (e.g. making bug fixes, adding new features) or image assets, requires you to recompile and redistribute the entire binary, which of course, includes any review time associated with the store(s) you are publishing to.
The CodePush plugin helps get product improvements in front of your end users instantly, by keeping your JavaScript and images synchronized with updates you release to the CodePush server. This way, your app gets the benefits of an offline mobile experience, as well as the "web-like" agility of side-loading updates as soon as they are available. It's a win-win!
In order to ensure that your end users always have a functioning version of your app, the CodePush plugin maintains a copy of the previous update, so that in the event that you accidentally push an update which includes a crash, it can automatically roll back. This way, you can rest assured that your newfound release agility won't result in users becoming blocked before you have a chance to [roll back](https://docs.microsoft.com/en-us/appcenter/distribution/codepush/cli#rolling-back-updates) on the server. It's a win-win-win!
*Note: Any product changes which touch native code (e.g. modifying your `AppDelegate.m`/`MainActivity.java` file, adding a new plugin) cannot be distributed via CodePush, and therefore, must be updated via the appropriate store(s).*
## Supported React Native platforms
- iOS (7+)
- Android (4.1+) on TLS 1.2 compatible devices
- Windows (UWP)
We try our best to maintain backwards compatibility of our plugin with previous versions of React Native, but due to the nature of the platform, and the existence of breaking changes between releases, it is possible that you need to use a specific version of the CodePush plugin in order to support the exact version of React Native you are using. The following table outlines which CodePush plugin versions officially support the respective React Native versions:
| React Native version(s) | Supporting CodePush version(s) |
|-------------------------|-------------------------------------------------------|
| <0.14 | **Unsupported** |
| v0.14 | v1.3 *(introduced Android support)* |
| v0.15-v0.18 | v1.4-v1.6 *(introduced iOS asset support)* |
| v0.19-v0.28 | v1.7-v1.17 *(introduced Android asset support)* |
| v0.29-v0.30 | v1.13-v1.17 *(RN refactored native hosting code)* |
| v0.31-v0.33 | v1.14.6-v1.17 *(RN refactored native hosting code)* |
| v0.34-v0.35 | v1.15-v1.17 *(RN refactored native hosting code)* |
| v0.36-v0.39 | v1.16-v1.17 *(RN refactored resume handler)* |
| v0.40-v0.42 | v1.17 *(RN refactored iOS header files)* |
| v0.43-v0.44 | v2.0+ *(RN refactored uimanager dependencies)* |
| v0.45 | v3.0+ *(RN refactored instance manager code)* |
| v0.46 | v4.0+ *(RN refactored js bundle loader code)* |
| v0.46-v0.53 | v5.1+ *(RN removed unused registration of JS modules)*|
| v0.54-v0.55 | v5.3+ *(Android Gradle Plugin 3.x integration)* |
| v0.56-v0.58 | v5.4+ *(RN upgraded versions for Android tools)* |
| v0.59 | v5.6+ *(RN refactored js bundle loader code)* |
| v0.60-v0.61 | v6.0+ *(RN migrated to Autolinking)* |
| v0.62-v0.64 | v6.2+ *(RN removed LiveReload)* |
| v0.65-v0.70 | v7.0+ *(RN updated iPhone-target-version)* |
| v0.71 | v8.0+ *(RN moved to react-native-gradle-plugin)* |
*NOTE: `react-native-code-push` versions lower than **[v5.7.0](https://github.com/microsoft/react-native-code-push/releases/tag/v5.7.0)** will stop working in the near future. You can find more information in our [documentation](https://github.com/microsoft/code-push/blob/master/migration-notice.md).*
We work hard to respond to new RN releases, but they do occasionally break us. We will update this chart with each RN release, so that users can check to see what our "official" support is.
### Supported Components
When using the React Native assets system (i.e. using the `require("./foo.png")` syntax), the following list represents the set of core components (and props) that support having their referenced images and videos updated via CodePush:
| Component | Prop(s) |
|-------------------------------------------------|------------------------------------------|
| `Image` | `source` |
| `MapView.Marker`
*(Requires [react-native-maps](https://github.com/lelandrichardson/react-native-maps) `>=O.3.2`)* | `image` |
| `ProgressViewIOS` | `progressImage`, `trackImage` |
| `TabBarIOS.Item` | `icon`, `selectedIcon` |
| `ToolbarAndroid`
*(React Native 0.21.0+)* | `actions[].icon`, `logo`, `overflowIcon` |
| `Video` | `source` |
The following list represents the set of components (and props) that don't currently support their assets being updated via CodePush, due to their dependency on static images and videos (i.e. using the `{ uri: "foo" }` syntax):
| Component | Prop(s) |
|-------------|----------------------------------------------------------------------|
| `SliderIOS` | `maximumTrackImage`, `minimumTrackImage`, `thumbImage`, `trackImage` |
| `Video` | `source` |
As new core components are released, which support referencing assets, we'll update this list to ensure users know what exactly they can expect to update using CodePush.
*Note: CodePush only works with Video components when using `require` in the source prop. For example:*
```javascript
```
## Getting Started
Once you've followed the general-purpose ["getting started"](https://docs.microsoft.com/en-us/appcenter/distribution/codepush/index) instructions for setting up your CodePush account, you can start CodePush-ifying your React Native app by running the following command from within your app's root directory:
```shell
npm install --save react-native-code-push
```
As with all other React Native plugins, the integration experience is different for iOS and Android, so perform the following setup steps depending on which platform(s) you are targeting. Note, if you are targeting both platforms it is recommended to create separate CodePush applications for each platform.
If you want to see how other projects have integrated with CodePush, you can check out the excellent [example apps](#example-apps--starters) provided by the community. Additionally, if you'd like to quickly familiarize yourself with CodePush + React Native, you can check out the awesome getting started videos produced by [Bilal Budhani](https://www.youtube.com/watch?v=uN0FRWk-YW8&feature=youtu.be) and/or [Deepak Sisodiya ](https://www.youtube.com/watch?v=f6I9y7V-Ibk).
*NOTE: This guide assumes you have used the `react-native init` command to initialize your React Native project. As of March 2017, the command `create-react-native-app` can also be used to initialize a React Native project. If using this command, please run `npm run eject` in your project's home directory to get a project very similar to what `react-native init` would have created.*
Then continue with installing the native module
* [iOS Setup](docs/setup-ios.md)
* [Android Setup](docs/setup-android.md)
* [Windows Setup](docs/setup-windows.md)
## Plugin Usage
With the CodePush plugin downloaded and linked, and your app asking CodePush where to get the right JS bundle from, the only thing left is to add the necessary code to your app to control the following policies:
1. When (and how often) to check for an update? (for example app start, in response to clicking a button in a settings page, periodically at some fixed interval)
2. When an update is available, how to present it to the end user?
The simplest way to do this is to "CodePush-ify" your app's root component. To do so, you can choose one of the following two options:
* **Option 1: Wrap your root component with the `codePush` higher-order component:**
* For class component
```javascript
import codePush from "react-native-code-push";
class MyApp extends Component {
}
MyApp = codePush(MyApp);
```
* For functional component
```javascript
import codePush from "react-native-code-push";
let MyApp: () => React$Node = () => {
}
MyApp = codePush(MyApp);
```
* **Option 2: Use the [ES7 decorator](https://github.com/wycats/javascript-decorators) syntax:**
*NOTE: Decorators are not yet supported in Babel 6.x pending proposal update.* You may need to enable it by installing and using [babel-preset-react-native-stage-0](https://github.com/skevy/babel-preset-react-native-stage-0#babel-preset-react-native-stage-0).
* For class component
```javascript
import codePush from "react-native-code-push";
@codePush
class MyApp extends Component {
}
```
* For functional component
```javascript
import codePush from "react-native-code-push";
const MyApp: () => React$Node = () => {
}
export default codePush(MyApp);
```
By default, CodePush will check for updates on every app start. If an update is available, it will be silently downloaded, and installed the next time the app is restarted (either explicitly by the end user or by the OS), which ensures the least invasive experience for your end users. If an available update is mandatory, then it will be installed immediately, ensuring that the end user gets it as soon as possible.
If you would like your app to discover updates more quickly, you can also choose to sync up with the CodePush server every time the app resumes from the background.
* For class component
```javascript
let codePushOptions = { checkFrequency: codePush.CheckFrequency.ON_APP_RESUME };
class MyApp extends Component {
}
MyApp = codePush(codePushOptions)(MyApp);
```
* For functional component
```javascript
let codePushOptions = { checkFrequency: codePush.CheckFrequency.ON_APP_RESUME };
let MyApp: () => React$Node = () => {
}
MyApp = codePush(codePushOptions)(MyApp);
```
Alternatively, if you want fine-grained control over when the check happens (like a button press or timer interval), you can call [`CodePush.sync()`](docs/api-js.md#codepushsync) at any time with your desired `SyncOptions`, and optionally turn off CodePush's automatic checking by specifying a manual `checkFrequency`:
```javascript
let codePushOptions = { checkFrequency: codePush.CheckFrequency.MANUAL };
class MyApp extends Component {
onButtonPress() {
codePush.sync({
updateDialog: true,
installMode: codePush.InstallMode.IMMEDIATE
});
}
render() {
return (
Check for updates
)
}
}
MyApp = codePush(codePushOptions)(MyApp);
```
If you would like to display an update confirmation dialog (an "active install"), configure when an available update is installed (like force an immediate restart) or customize the update experience in any other way, refer to the [`codePush()`](docs/api-js.md#codepush) API reference for information on how to tweak this default behavior.
*NOTE: If you are using [Redux](http://redux.js.org) and [Redux Saga](https://redux-saga.js.org/), you can alternatively use the [react-native-code-push-saga](http://github.com/lostintangent/react-native-code-push-saga) module, which allows you to customize when `sync` is called in a perhaps simpler/more idiomatic way.*
### Store Guideline Compliance
Android Google Play and iOS App Store have corresponding guidelines that have rules you should be aware of before integrating the CodePush solution within your application.
#### Google play
Third paragraph of [Device and Network Abuse](https://support.google.com/googleplay/android-developer/answer/9888379?hl=en) topic describe that updating source code by any method other than Google Play's update mechanism is restricted. But this restriction does not apply to updating javascript bundles.
> This restriction does not apply to code that runs in a virtual machine and has limited access to Android APIs (such as JavaScript in a webview or browser).
That fully allow CodePush as it updates just JS bundles and can't update native code part.
#### App Store
Paragraph **3.3.2**, since back in 2015's [Apple Developer Program License Agreement](https://developer.apple.com/programs/ios/information/) fully allowed performing over-the-air updates of JavaScript and assets - and in its latest version (20170605) [downloadable here](https://developer.apple.com/terms/) this ruling is even broader:
> Interpreted code may be downloaded to an Application but only so long as such code: (a) does not change the primary purpose of the Application by providing features or functionality that are inconsistent with the intended and advertised purpose of the Application as submitted to the App Store, (b) does not create a store or storefront for other code or applications, and (c) does not bypass signing, sandbox, or other security features of the OS.
CodePush allows you to follow these rules in full compliance so long as the update you push does not significantly deviate your product from its original App Store approved intent.
To further remain in compliance with Apple's guidelines we suggest that App Store-distributed apps don't enable the `updateDialog` option when calling `sync`, since in the [App Store Review Guidelines](https://developer.apple.com/app-store/review/guidelines/) it is written that:
> Apps must not force users to rate the app, review the app, download other apps, or other similar actions in order to access functionality, content, or use of the app.
This is not necessarily the case for `updateDialog`, since it won't force the user to download the new version, but at least you should be aware of that ruling if you decide to show it.
## Releasing Updates
Once your app is configured and distributed to your users, and you have made some JS or asset changes, it's time to release them. The recommended way to release them is using the `release-react` command in the App Center CLI, which will bundle your JavaScript files, asset files, and release the update to the CodePush server.
*NOTE: Before you can start releasing updates, please log into App Center by running the `appcenter login` command.*
In its most basic form, this command only requires one parameter: your owner name + "/" + app name.
```shell
appcenter codepush release-react -a /
appcenter codepush release-react -a /MyApp-iOS
appcenter codepush release-react -a /MyApp-Android
```
The `release-react` command enables such a simple workflow because it provides many sensible defaults (like generating a release bundle, assuming your app's entry file on iOS is either `index.ios.js` or `index.js`). However, all of these defaults can be customized to allow incremental flexibility as necessary, which makes it a good fit for most scenarios.
```shell
# Release a mandatory update with a changelog
appcenter codepush release-react -a /MyApp-iOS -m --description "Modified the header color"
# Release an update for an app that uses a non-standard entry file name, and also capture
# the sourcemap file generated by react-native bundle
appcenter codepush release-react -a /MyApp-iOS --entry-file MyApp.js --sourcemap-output ../maps/MyApp.map
# Release a dev Android build to just 1/4 of your end users
appcenter codepush release-react -a /MyApp-Android --rollout 25 --development true
# Release an update that targets users running any 1.1.* binary, as opposed to
# limiting the update to exact version name in the build.gradle file
appcenter codepush release-react -a /MyApp-Android --target-binary-version "~1.1.0"
```
The CodePush client supports differential updates, so even though you are releasing your JS bundle and assets on every update, your end users will only actually download the files they need. The service handles this automatically so that you can focus on creating awesome apps and we can worry about optimizing end user downloads.
For more details about how the `release-react` command works, as well as the various parameters it exposes, refer to the [CLI docs](https://github.com/microsoft/code-push/tree/v3.0.1/cli#releasing-updates-react-native). Additionally, if you would prefer to handle running the `react-native bundle` command yourself, and therefore, want an even more flexible solution than `release-react`, refer to the [`release` command](https://github.com/microsoft/code-push/tree/v3.0.1/cli#releasing-updates-general) for more details.
If you run into any issues, or have any questions/comments/feedback, you can ping us within the [#code-push](https://discord.gg/0ZcbPKXt5bWxFdFu) channel on Reactiflux, [e-mail us](mailto:codepushfeed@microsoft.com) and/or check out the [troubleshooting](#debugging--troubleshooting) details below.
*NOTE: CodePush updates should be tested in modes other than Debug mode. In Debug mode, React Native app always downloads JS bundle generated by packager, so JS bundle downloaded by CodePush does not apply.*
### Multi-Deployment Testing
In our [getting started](#getting-started) docs, we illustrated how to configure the CodePush plugin using a specific deployment key. However, in order to effectively test your releases, it is critical that you leverage the `Staging` and `Production` deployments that are auto-generated when you first created your CodePush app (or any custom deployments you may have created). This way, you never release an update to your end users that you haven't been able to validate yourself.
*NOTE: Our client-side rollback feature can help unblock users after installing a release that resulted in a crash, and server-side rollbacks (i.e. `appcenter codepush rollback`) allow you to prevent additional users from installing a bad release once it's been identified. However, it's obviously better if you can prevent an erroneous update from being broadly released in the first place.*
Taking advantage of the `Staging` and `Production` deployments allows you to achieve a workflow like the following (feel free to customize!):
1. Release a CodePush update to your `Staging` deployment using the `appcenter codepush release-react` command (or `appcenter codepush release` if you need more control)
2. Run your staging/beta build of your app, sync the update from the server, and verify it works as expected
3. Promote the tested release from `Staging` to `Production` using the `appcenter codepush promote` command
4. Run your production/release build of your app, sync the update from the server and verify it works as expected
*NOTE: If you want to take a more cautious approach, you can even choose to perform a "staged rollout" as part of #3, which allows you to mitigate additional potential risk with the update (like did your testing in #2 touch all possible devices/conditions?) by only making the production update available to a percentage of your users (for example `appcenter codepush promote -a / -s Staging -d Production -r 20`). Then, after waiting for a reasonable amount of time to see if any crash reports or customer feedback comes in, you can expand it to your entire audience by running `appcenter codepush patch -a / Production -r 100`.*
You'll notice that the above steps refer to a "staging build" and "production build" of your app. If your build process already generates distinct binaries per "environment", then you don't need to read any further, since swapping out CodePush deployment keys is just like handling environment-specific config for any other service your app uses (like Facebook). However, if you're looking for examples (**including demo projects**) on how to setup your build process to accommodate this, then refer to the following sections, depending on the platform(s) your app is targeting:
* [Android](docs/multi-deployment-testing-android.md)
* [iOS](docs/multi-deployment-testing-ios.md)
### Dynamic Deployment Assignment
The above section illustrated how you can leverage multiple CodePush deployments in order to effectively test your updates before broadly releasing them to your end users. However, since that workflow statically embeds the deployment assignment into the actual binary, a staging or production build will only ever sync updates from that deployment. In many cases, this is sufficient, since you only want your team, customers, stakeholders, etc. to sync with your pre-production releases, and therefore, only they need a build that knows how to sync with staging. However, if you want to be able to perform A/B tests, or provide early access of your app to certain users, it can prove very useful to be able to dynamically place specific users (or audiences) into specific deployments at runtime.
In order to achieve this kind of workflow, all you need to do is specify the deployment key you want the current user to syncronize with when calling the `codePush` method. When specified, this key will override the "default" one that was provided in your app's `Info.plist` (iOS) or `MainActivity.java` (Android) files. This allows you to produce a build for staging or production, that is also capable of being dynamically "redirected" as needed.
```javascript
// Imagine that "userProfile" is a prop that this component received
// which includes the deployment key that the current user should use.
codePush.sync({ deploymentKey: userProfile.CODEPUSH_KEY });
```
With that change in place, now it's just a matter of choosing how your app determines the right deployment key for the current user. In practice, there are typically two solutions for this:
1. Expose a user-visible mechanism for changing deployments at any time. For example, your settings page could have a toggle for enabling "beta" access. This model works well if you're not concerned with the privacy of your pre-production updates, and you have power users that may want to opt-in to earlier (and potentially buggy) updates at their own will (kind of like Chrome channels). However, this solution puts the decision in the hands of your users, which doesn't help you perform A/B tests transparently.
2. Annotate the server-side profile of your users with an additional piece of metadata that indicates the deployment they should sync with. By default, your app could just use the binary-embedded key, but after a user has authenticated, your server can choose to "redirect" them to a different deployment, which allows you to incrementally place certain users or groups in different deployments as needed. You could even choose to store the server-response in local storage so that it becomes the new default. How you store the key alongside your user's profiles is entirely up to your authentication solution (for example Auth0, Firebase, custom DB + REST API), but is generally pretty trivial to do.
*NOTE: If needed, you could also implement a hybrid solution that allowed your end-users to toggle between different deployments, while also allowing your server to override that decision. This way, you have a hierarchy of "deployment resolution" that ensures your app has the ability to update itself out-of-the-box, your end users can feel rewarded by getting early access to bits, but you also have the ability to run A/B tests on your users as needed.*
Since we recommend using the `Staging` deployment for pre-release testing of your updates (as explained in the previous section), it doesn't neccessarily make sense to use it for performing A/B tests on your users, as opposed to allowing early-access (as explained in option #1 above). Therefore, we recommend making full use of custom app deployments, so that you can segment your users however makes sense for your needs. For example, you could create long-term or even one-off deployments, release a variant of your app to it, and then place certain users into it in order to see how they engage.
```javascript
// #1) Create your new deployment to hold releases of a specific app variant
appcenter codepush deployment add -a / test-variant-one
// #2) Target any new releases at that custom deployment
appcenter codepush release-react -a / -d test-variant-one
```
*NOTE: The total user count that is reported in your deployment's "Install Metrics" will take into account users that have "switched" from one deployment to another. For example, if your `Production` deployment currently reports having 1 total user, but you dynamically switch that user to `Staging`, then the `Production` deployment would report 0 total users, while `Staging` would report 1 (the user that just switched). This behavior allows you to accurately track your release adoption, even in the event of using a runtime-based deployment redirection solution.*
---
## API Reference
* [JavaScript API](docs/api-js.md)
* [Objective-C API Reference (iOS)](docs/api-ios.md)
* [Java API Reference (Android)](docs/api-android.md)
### Example Apps / Starters
The React Native community has graciously created some awesome open source apps that can serve as examples for developers that are getting started. The following is a list of OSS React Native apps that are also using CodePush, and can therefore be used to see how others are using the service:
* [F8 App](https://github.com/fbsamples/f8app) - The official conference app for [F8 2016](https://www.fbf8.com/).
* [Feline for Product Hunt](https://github.com/arjunkomath/Feline-for-Product-Hunt) - An Android client for Product Hunt.
* [GeoEncoding](https://github.com/LynxITDigital/GeoEncoding) - An app by [Lynx IT Digital](https://digital.lynxit.com.au) which demonstrates how to use numerous React Native components and modules.
* [Math Facts](https://github.com/Khan/math-facts) - An app by Khan Academy to help memorize math facts more easily.
Additionally, if you're looking to get started with React Native + CodePush, and are looking for an awesome starter kit, you should check out the following:
* [Pepperoni](http://getpepperoni.com/)
*Note: If you've developed a React Native app using CodePush, that is also open-source, please let us know. We would love to add it to this list!*
### Debugging / Troubleshooting
The `sync` method includes a lot of diagnostic logging out-of-the-box, so if you're encountering an issue when using it, the best thing to try first is examining the output logs of your app. This will tell you whether the app is configured correctly (like can the plugin find your deployment key?), if the app is able to reach the server, if an available update is being discovered, if the update is being successfully downloaded/installed, etc. We want to continue improving the logging to be as intuitive/comprehensive as possible, so please [let us know](mailto:codepushfeed@microsoft.com) if you find it to be confusing or missing anything.
The simplest way to view these logs is to add the flag `--debug` for each command. This will output a log stream that is filtered to just CodePush messages. This makes it easy to identify issues, without needing to use a platform-specific tool, or wade through a potentially high volume of logs.
Additionally, you can also use any of the platform-specific tools to view the CodePush logs, if you are more comfortable with them. Simple start up the Chrome DevTools Console, the Xcode Console (iOS), the [OS X Console](https://en.wikipedia.org/wiki/Console_%28OS_X%29#.7E.2FLibrary.2FLogs) (iOS) and/or ADB logcat (Android), and look for messages which are prefixed with `[CodePush]`.
Note that by default, React Native logs are disabled on iOS in release builds, so if you want to view them in a release build, you need to make the following changes to your `AppDelegate.m` file:
1. Add an `#import ` statement. For RN < v0.40 use: `#import "RCTLog.h"`
2. Add the following statement to the top of your `application:didFinishLaunchingWithOptions` method:
```objective-c
RCTSetLogThreshold(RCTLogLevelInfo);
```
Now you'll be able to see CodePush logs in either debug or release mode, on both iOS or Android. If examining the logs don't provide an indication of the issue, please refer to the following common issues for additional resolution ideas:
| Issue / Symptom | Possible Solution |
|-----------------|-------------------|
| Compilation Error | Double-check that your version of React Native is [compatible](#supported-react-native-platforms) with the CodePush version you are using. |
| Network timeout / hang when calling `sync` or `checkForUpdate` in the iOS Simulator | Try resetting the simulator by selecting the `Simulator -> Reset Content and Settings..` menu item, and then re-running your app. |
| Server responds with a `404` when calling `sync` or `checkForUpdate` | Double-check that the deployment key you added to your `Info.plist` (iOS), `build.gradle` (Android) or that you're passing to `sync`/`checkForUpdate`, is in fact correct. You can run `appcenter codepush deployment list / --displayKeys` to view the correct keys for your app deployments. |
| Update not being discovered | Double-check that the version of your running app (like `1.0.0`) matches the version you specified when releasing the update to CodePush. Additionally, make sure that you are releasing to the same deployment that your app is configured to sync with. |
| Update not being displayed after restart | If you're not calling `sync` on app start (like within `componentDidMount` of your root component), then you need to explicitly call `notifyApplicationReady` on app start, otherwise, the plugin will think your update failed and roll it back. |
| I've released an update for iOS but my Android app also shows an update and it breaks it | Be sure you have different deployment keys for each platform in order to receive updates correctly |
| I've released new update but changes are not reflected | Be sure that you are running app in modes other than Debug. In Debug mode, React Native app always downloads JS bundle generated by packager, so JS bundle downloaded by CodePush does not apply.
| No JS bundle is being found when running your app against the iOS simulator | By default, React Native doesn't generate your JS bundle when running against the simulator. Therefore, if you're using `[CodePush bundleURL]`, and targetting the iOS simulator, you may be getting a `nil` result. This issue will be fixed in RN 0.22.0, but only for release builds. You can unblock this scenario right now by making [this change](https://github.com/facebook/react-native/commit/9ae3714f4bebdd2bcab4d7fdbf23acebdc5ed2ba) locally.
### Continuous Integration / Delivery
In addition to being able to use the CodePush CLI to "manually" release updates, we believe that it's important to create a repeatable and sustainable solution for contiously delivering updates to your app. That way, it's simple enough for you and/or your team to create and maintain the rhythm of performing agile deployments. In order to assist with setting up a CodePush-based CD pipeline, refer to the following integrations with various CI servers:
* [Visual Studio Team Services](https://marketplace.visualstudio.com/items?itemName=ms-vsclient.code-push) - *NOTE: VSTS also has extensions for publishing to [HockeyApp](https://marketplace.visualstudio.com/items?itemName=ms.hockeyapp) and the [Google Play](https://github.com/microsoft/google-play-vsts-extension) store, so it provides a pretty great mobile CD solution in general.*
* [Travis CI](https://github.com/mondora/code-push-travis-cli)
Additionally, if you'd like more details of what a complete mobile CI/CD workflow can look like, which includes CodePush, check out this [excellent article](https://medium.com/zeemee-engineering/zeemee-engineering-and-the-quest-for-the-holy-mobile-dev-grail-1310be4953d1) by the [ZeeMee engineering team](https://www.zeemee.com/).
### TypeScript Consumption
This module ships its `*.d.ts` file as part of its NPM package, which allows you to simply `import` it, and receive intellisense in supporting editors (like Visual Studio Code), as well as compile-time type checking if you're using TypeScript. For the most part, this behavior should just work out of the box, however, if you've specified `es6` as the value for either the `target` or `module` [compiler option](http://www.typescriptlang.org/docs/handbook/compiler-options.html) in your [`tsconfig.json`](http://www.typescriptlang.org/docs/handbook/tsconfig-json.html) file, then just make sure that you also set the `moduleResolution` option to `node`. This ensures that the TypeScript compiler will look within the `node_modules` for the type definitions of imported modules. Otherwise, you'll get an error like the following when trying to import the `react-native-code-push` module: `error TS2307: Cannot find module 'react-native-code-push'`.
---
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
================================================
FILE: Recipes/UpdateButton.ios.js
================================================
'use strict';
var pkg = require('./package');
var React = require('react-native');
var {
AppRegistry,
StyleSheet,
Text,
View,
} = React;
var Button = require('react-native-button');
var CodePush = require('react-native-code-push');
var UpdateButton = React.createClass({
getInitialState: function() {
return {};
},
componentDidMount: function() {
CodePush.checkForUpdate().done((update) => {
if (update && !update.downloadURL) {
this.setState({
update: update
});
}
});
},
update: function() {
this.state.update.download().done((newPackage) => {
newPackage.install();
});
},
render: function() {
var updateButton = null;
if (this.state.update) {
updateButton = ;
}
return (
Welcome to {pkg.name} {pkg.version}!
{updateButton}
);
}
});
var styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
}
});
AppRegistry.registerComponent('UpdateButton', () => UpdateButton);
================================================
FILE: Recipes/UpdateOnStart.ios.js
================================================
'use strict';
var pkg = require('./package');
var React = require('react-native');
var {
AppRegistry,
StyleSheet,
Text,
View,
} = React;
var CodePush = require('react-native-code-push');
var UpdateOnStart = React.createClass({
componentDidMount: function() {
CodePush.checkForUpdate().done((update) => {
if (update && update.downloadUrl) {
update.download().done((newPackage) => {
newPackage.install();
});
}
});
},
render: function() {
return (
Welcome to {pkg.name} {pkg.version}!
);
}
});
var styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
}
});
AppRegistry.registerComponent('UpdateOnStart', () => UpdateOnStart);
================================================
FILE: SECURITY.md
================================================
## Security
Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/).
If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://docs.microsoft.com/en-us/previous-versions/tn-archive/cc751383(v=technet.10)), please report it to us as described below.
## Reporting Security Issues
**Please do not report security vulnerabilities through public GitHub issues.**
Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://msrc.microsoft.com/create-report).
If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://www.microsoft.com/en-us/msrc/pgp-key-msrc).
You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc).
Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue:
* Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.)
* Full paths of source file(s) related to the manifestation of the issue
* The location of the affected source code (tag/branch/commit or direct URL)
* Any special configuration required to reproduce the issue
* Step-by-step instructions to reproduce the issue
* Proof-of-concept or exploit code (if possible)
* Impact of the issue, including how an attacker might exploit the issue
This information will help us triage your report more quickly.
If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://microsoft.com/msrc/bounty) page for more details about our active programs.
## Preferred Languages
We prefer all communications to be in English.
## Policy
Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://www.microsoft.com/en-us/msrc/cvd).
================================================
FILE: android/app/build.gradle
================================================
apply plugin: "com.android.library"
def DEFAULT_COMPILE_SDK_VERSION = 26
def DEFAULT_BUILD_TOOLS_VERSION = "26.0.3"
def DEFAULT_TARGET_SDK_VERSION = 26
def DEFAULT_MIN_SDK_VERSION = 16
android {
namespace "com.microsoft.codepush.react"
compileSdkVersion rootProject.hasProperty('compileSdkVersion') ? rootProject.compileSdkVersion : DEFAULT_COMPILE_SDK_VERSION
buildToolsVersion rootProject.hasProperty('buildToolsVersion') ? rootProject.buildToolsVersion : DEFAULT_BUILD_TOOLS_VERSION
defaultConfig {
minSdkVersion rootProject.hasProperty('minSdkVersion') ? rootProject.minSdkVersion : DEFAULT_MIN_SDK_VERSION
targetSdkVersion rootProject.hasProperty('targetSdkVersion') ? rootProject.targetSdkVersion : DEFAULT_TARGET_SDK_VERSION
versionCode 1
versionName "1.0"
}
lintOptions {
abortOnError false
}
defaultConfig {
consumerProguardFiles 'proguard-rules.pro'
}
}
dependencies {
implementation "com.facebook.react:react-native:+"
implementation 'com.nimbusds:nimbus-jose-jwt:9.37.3'
}
================================================
FILE: android/app/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Invoked via reflection, when setting js bundle.
-keepclassmembers class com.facebook.react.ReactInstanceManager {
private final ** mBundleLoader;
}
# Can't find referenced class org.bouncycastle.**
-dontwarn com.nimbusds.jose.**
================================================
FILE: android/app/src/debug/AndroidManifest.xml
================================================
================================================
FILE: android/app/src/main/AndroidManifest.xml
================================================
================================================
FILE: android/app/src/main/java/com/microsoft/codepush/react/CodePush.java
================================================
package com.microsoft.codepush.react;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.devsupport.interfaces.DevSupportManager;
import com.facebook.react.modules.debug.interfaces.DeveloperSettings;
import com.facebook.react.uimanager.ViewManager;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.lang.reflect.Method;
public class CodePush implements ReactPackage {
private static boolean sIsRunningBinaryVersion = false;
private static boolean sNeedToReportRollback = false;
private static boolean sTestConfigurationFlag = false;
private static String sAppVersion = null;
private boolean mDidUpdate = false;
private String mAssetsBundleFileName;
// Helper classes.
private CodePushUpdateManager mUpdateManager;
private CodePushTelemetryManager mTelemetryManager;
private SettingsManager mSettingsManager;
// Config properties.
private String mDeploymentKey;
private static String mServerUrl = "https://codepush.appcenter.ms/";
private Context mContext;
private final boolean mIsDebugMode;
private static String mPublicKey;
private static ReactInstanceHolder mReactInstanceHolder;
private static CodePush mCurrentInstance;
public CodePush(String deploymentKey, Context context) {
this(deploymentKey, context, false);
}
public static String getServiceUrl() {
return mServerUrl;
}
public CodePush(String deploymentKey, Context context, boolean isDebugMode) {
mContext = context.getApplicationContext();
mUpdateManager = new CodePushUpdateManager(context.getFilesDir().getAbsolutePath());
mTelemetryManager = new CodePushTelemetryManager(mContext);
mDeploymentKey = deploymentKey;
mIsDebugMode = isDebugMode;
mSettingsManager = new SettingsManager(mContext);
if (sAppVersion == null) {
try {
PackageInfo pInfo = mContext.getPackageManager().getPackageInfo(mContext.getPackageName(), 0);
sAppVersion = pInfo.versionName;
} catch (PackageManager.NameNotFoundException e) {
throw new CodePushUnknownException("Unable to get package info for " + mContext.getPackageName(), e);
}
}
mCurrentInstance = this;
String publicKeyFromStrings = getCustomPropertyFromStringsIfExist("PublicKey");
if (publicKeyFromStrings != null) mPublicKey = publicKeyFromStrings;
String serverUrlFromStrings = getCustomPropertyFromStringsIfExist("ServerUrl");
if (serverUrlFromStrings != null) mServerUrl = serverUrlFromStrings;
clearDebugCacheIfNeeded(null);
initializeUpdateAfterRestart();
}
public CodePush(String deploymentKey, Context context, boolean isDebugMode, String serverUrl) {
this(deploymentKey, context, isDebugMode);
mServerUrl = serverUrl;
}
public CodePush(String deploymentKey, Context context, boolean isDebugMode, int publicKeyResourceDescriptor) {
this(deploymentKey, context, isDebugMode);
mPublicKey = getPublicKeyByResourceDescriptor(publicKeyResourceDescriptor);
}
public CodePush(String deploymentKey, Context context, boolean isDebugMode, String serverUrl, Integer publicKeyResourceDescriptor) {
this(deploymentKey, context, isDebugMode);
if (publicKeyResourceDescriptor != null) {
mPublicKey = getPublicKeyByResourceDescriptor(publicKeyResourceDescriptor);
}
mServerUrl = serverUrl;
}
private String getPublicKeyByResourceDescriptor(int publicKeyResourceDescriptor){
String publicKey;
try {
publicKey = mContext.getString(publicKeyResourceDescriptor);
} catch (Resources.NotFoundException e) {
throw new CodePushInvalidPublicKeyException(
"Unable to get public key, related resource descriptor " +
publicKeyResourceDescriptor +
" can not be found", e
);
}
if (publicKey.isEmpty()) {
throw new CodePushInvalidPublicKeyException("Specified public key is empty");
}
return publicKey;
}
private String getCustomPropertyFromStringsIfExist(String propertyName) {
String property;
String packageName = mContext.getPackageName();
int resId = mContext.getResources().getIdentifier("CodePush" + propertyName, "string", packageName);
if (resId != 0) {
property = mContext.getString(resId);
if (!property.isEmpty()) {
return property;
} else {
CodePushUtils.log("Specified " + propertyName + " is empty");
}
}
return null;
}
private boolean isLiveReloadEnabled(ReactInstanceManager instanceManager) {
// Use instanceManager for checking if we use LiveReload mode. In this case we should not remove ReactNativeDevBundle.js file
// because we get error with trying to get this after reloading. Issue: https://github.com/microsoft/react-native-code-push/issues/1272
if (instanceManager != null) {
DevSupportManager devSupportManager = instanceManager.getDevSupportManager();
if (devSupportManager != null) {
DeveloperSettings devSettings = devSupportManager.getDevSettings();
Method[] methods = devSettings.getClass().getMethods();
for (Method m : methods) {
if (m.getName().equals("isReloadOnJSChangeEnabled")) {
try {
return (boolean) m.invoke(devSettings);
} catch (Exception x) {
return false;
}
}
}
}
}
return false;
}
public void clearDebugCacheIfNeeded(ReactInstanceManager instanceManager) {
if (mIsDebugMode && mSettingsManager.isPendingUpdate(null) && !isLiveReloadEnabled(instanceManager)) {
// This needs to be kept in sync with https://github.com/facebook/react-native/blob/master/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManager.java#L78
File cachedDevBundle = new File(mContext.getFilesDir(), "ReactNativeDevBundle.js");
if (cachedDevBundle.exists()) {
cachedDevBundle.delete();
}
}
}
public boolean didUpdate() {
return mDidUpdate;
}
public String getAppVersion() {
return sAppVersion;
}
public String getAssetsBundleFileName() {
return mAssetsBundleFileName;
}
public String getPublicKey() {
return mPublicKey;
}
long getBinaryResourcesModifiedTime() {
try {
String packageName = this.mContext.getPackageName();
int codePushApkBuildTimeId = this.mContext.getResources().getIdentifier(CodePushConstants.CODE_PUSH_APK_BUILD_TIME_KEY, "string", packageName);
// replace double quotes needed for correct restoration of long value from strings.xml
// https://github.com/microsoft/cordova-plugin-code-push/issues/264
String codePushApkBuildTime = this.mContext.getResources().getString(codePushApkBuildTimeId).replaceAll("\"","");
return Long.parseLong(codePushApkBuildTime);
} catch (Exception e) {
throw new CodePushUnknownException("Error in getting binary resources modified time", e);
}
}
public String getPackageFolder() {
JSONObject codePushLocalPackage = mUpdateManager.getCurrentPackage();
if (codePushLocalPackage == null) {
return null;
}
return mUpdateManager.getPackageFolderPath(codePushLocalPackage.optString("packageHash"));
}
@Deprecated
public static String getBundleUrl() {
return getJSBundleFile();
}
@Deprecated
public static String getBundleUrl(String assetsBundleFileName) {
return getJSBundleFile(assetsBundleFileName);
}
public Context getContext() {
return mContext;
}
public String getDeploymentKey() {
return mDeploymentKey;
}
public static String getJSBundleFile() {
return CodePush.getJSBundleFile(CodePushConstants.DEFAULT_JS_BUNDLE_NAME);
}
public static String getJSBundleFile(String assetsBundleFileName) {
if (mCurrentInstance == null) {
throw new CodePushNotInitializedException("A CodePush instance has not been created yet. Have you added it to your app's list of ReactPackages?");
}
return mCurrentInstance.getJSBundleFileInternal(assetsBundleFileName);
}
public String getJSBundleFileInternal(String assetsBundleFileName) {
this.mAssetsBundleFileName = assetsBundleFileName;
String binaryJsBundleUrl = CodePushConstants.ASSETS_BUNDLE_PREFIX + assetsBundleFileName;
String packageFilePath = null;
try {
packageFilePath = mUpdateManager.getCurrentPackageBundlePath(this.mAssetsBundleFileName);
} catch (CodePushMalformedDataException e) {
// We need to recover the app in case 'codepush.json' is corrupted
CodePushUtils.log(e.getMessage());
clearUpdates();
}
if (packageFilePath == null) {
// There has not been any downloaded updates.
CodePushUtils.logBundleUrl(binaryJsBundleUrl);
sIsRunningBinaryVersion = true;
return binaryJsBundleUrl;
}
JSONObject packageMetadata = this.mUpdateManager.getCurrentPackage();
if (isPackageBundleLatest(packageMetadata)) {
CodePushUtils.logBundleUrl(packageFilePath);
sIsRunningBinaryVersion = false;
return packageFilePath;
} else {
// The binary version is newer.
this.mDidUpdate = false;
if (!this.mIsDebugMode || hasBinaryVersionChanged(packageMetadata)) {
this.clearUpdates();
}
CodePushUtils.logBundleUrl(binaryJsBundleUrl);
sIsRunningBinaryVersion = true;
return binaryJsBundleUrl;
}
}
public String getServerUrl() {
return mServerUrl;
}
void initializeUpdateAfterRestart() {
// Reset the state which indicates that
// the app was just freshly updated.
mDidUpdate = false;
JSONObject pendingUpdate = mSettingsManager.getPendingUpdate();
if (pendingUpdate != null) {
JSONObject packageMetadata = null;
try {
packageMetadata = this.mUpdateManager.getCurrentPackage();
} catch (CodePushMalformedDataException e) {
// We need to recover the app in case 'codepush.json' is corrupted
CodePushUtils.log(e);
clearUpdates();
return;
}
if (packageMetadata == null || !isPackageBundleLatest(packageMetadata) && hasBinaryVersionChanged(packageMetadata)) {
CodePushUtils.log("Skipping initializeUpdateAfterRestart(), binary version is newer");
return;
}
try {
boolean updateIsLoading = pendingUpdate.getBoolean(CodePushConstants.PENDING_UPDATE_IS_LOADING_KEY);
if (updateIsLoading) {
// Pending update was initialized, but notifyApplicationReady was not called.
// Therefore, deduce that it is a broken update and rollback.
CodePushUtils.log("Update did not finish loading the last time, rolling back to a previous version.");
sNeedToReportRollback = true;
rollbackPackage();
} else {
// There is in fact a new update running for the first
// time, so update the local state to ensure the client knows.
mDidUpdate = true;
// Mark that we tried to initialize the new update, so that if it crashes,
// we will know that we need to rollback when the app next starts.
mSettingsManager.savePendingUpdate(pendingUpdate.getString(CodePushConstants.PENDING_UPDATE_HASH_KEY),
/* isLoading */true);
}
} catch (JSONException e) {
// Should not happen.
throw new CodePushUnknownException("Unable to read pending update metadata stored in SharedPreferences", e);
}
}
}
void invalidateCurrentInstance() {
mCurrentInstance = null;
}
boolean isDebugMode() {
return mIsDebugMode;
}
boolean isRunningBinaryVersion() {
return sIsRunningBinaryVersion;
}
private boolean isPackageBundleLatest(JSONObject packageMetadata) {
try {
Long binaryModifiedDateDuringPackageInstall = null;
String binaryModifiedDateDuringPackageInstallString = packageMetadata.optString(CodePushConstants.BINARY_MODIFIED_TIME_KEY, null);
if (binaryModifiedDateDuringPackageInstallString != null) {
binaryModifiedDateDuringPackageInstall = Long.parseLong(binaryModifiedDateDuringPackageInstallString);
}
String packageAppVersion = packageMetadata.optString("appVersion", null);
long binaryResourcesModifiedTime = this.getBinaryResourcesModifiedTime();
return binaryModifiedDateDuringPackageInstall != null &&
binaryModifiedDateDuringPackageInstall == binaryResourcesModifiedTime &&
(isUsingTestConfiguration() || sAppVersion.equals(packageAppVersion));
} catch (NumberFormatException e) {
throw new CodePushUnknownException("Error in reading binary modified date from package metadata", e);
}
}
private boolean hasBinaryVersionChanged(JSONObject packageMetadata) {
String packageAppVersion = packageMetadata.optString("appVersion", null);
return !sAppVersion.equals(packageAppVersion);
}
boolean needToReportRollback() {
return sNeedToReportRollback;
}
public static void overrideAppVersion(String appVersionOverride) {
sAppVersion = appVersionOverride;
}
private void rollbackPackage() {
JSONObject failedPackage = mUpdateManager.getCurrentPackage();
mSettingsManager.saveFailedUpdate(failedPackage);
mUpdateManager.rollbackPackage();
mSettingsManager.removePendingUpdate();
}
public void setNeedToReportRollback(boolean needToReportRollback) {
CodePush.sNeedToReportRollback = needToReportRollback;
}
/* The below 3 methods are used for running tests.*/
public static boolean isUsingTestConfiguration() {
return sTestConfigurationFlag;
}
public void setDeploymentKey(String deploymentKey) {
mDeploymentKey = deploymentKey;
}
public static void setUsingTestConfiguration(boolean shouldUseTestConfiguration) {
sTestConfigurationFlag = shouldUseTestConfiguration;
}
public void clearUpdates() {
mUpdateManager.clearUpdates();
mSettingsManager.removePendingUpdate();
mSettingsManager.removeFailedUpdates();
}
public static void setReactInstanceHolder(ReactInstanceHolder reactInstanceHolder) {
mReactInstanceHolder = reactInstanceHolder;
}
static ReactInstanceManager getReactInstanceManager() {
if (mReactInstanceHolder == null) {
return null;
}
return mReactInstanceHolder.getReactInstanceManager();
}
@Override
public List createNativeModules(ReactApplicationContext reactApplicationContext) {
CodePushNativeModule codePushModule = new CodePushNativeModule(reactApplicationContext, this, mUpdateManager, mTelemetryManager, mSettingsManager);
CodePushDialog dialogModule = new CodePushDialog(reactApplicationContext);
List nativeModules = new ArrayList<>();
nativeModules.add(codePushModule);
nativeModules.add(dialogModule);
return nativeModules;
}
// Deprecated in RN v0.47.
public List> createJSModules() {
return new ArrayList<>();
}
@Override
public List createViewManagers(ReactApplicationContext reactApplicationContext) {
return new ArrayList<>();
}
}
================================================
FILE: android/app/src/main/java/com/microsoft/codepush/react/CodePushBuilder.java
================================================
package com.microsoft.codepush.react;
import android.content.Context;
public class CodePushBuilder {
private String mDeploymentKey;
private Context mContext;
private boolean mIsDebugMode;
private String mServerUrl;
private Integer mPublicKeyResourceDescriptor;
public CodePushBuilder(String deploymentKey, Context context) {
this.mDeploymentKey = deploymentKey;
this.mContext = context;
this.mServerUrl = CodePush.getServiceUrl();
}
public CodePushBuilder setIsDebugMode(boolean isDebugMode) {
this.mIsDebugMode = isDebugMode;
return this;
}
public CodePushBuilder setServerUrl(String serverUrl) {
this.mServerUrl = serverUrl;
return this;
}
public CodePushBuilder setPublicKeyResourceDescriptor(int publicKeyResourceDescriptor) {
this.mPublicKeyResourceDescriptor = publicKeyResourceDescriptor;
return this;
}
public CodePush build() {
return new CodePush(this.mDeploymentKey, this.mContext, this.mIsDebugMode, this.mServerUrl, this.mPublicKeyResourceDescriptor);
}
}
================================================
FILE: android/app/src/main/java/com/microsoft/codepush/react/CodePushConstants.java
================================================
package com.microsoft.codepush.react;
public class CodePushConstants {
public static final String ASSETS_BUNDLE_PREFIX = "assets://";
public static final String BINARY_MODIFIED_TIME_KEY = "binaryModifiedTime";
public static final String CODE_PUSH_FOLDER_PREFIX = "CodePush";
public static final String CODE_PUSH_HASH_FILE_NAME = "CodePushHash";
public static final String CODE_PUSH_OLD_HASH_FILE_NAME = "CodePushHash.json";
public static final String CODE_PUSH_PREFERENCES = "CodePush";
public static final String CURRENT_PACKAGE_KEY = "currentPackage";
public static final String DEFAULT_JS_BUNDLE_NAME = "index.android.bundle";
public static final String DIFF_MANIFEST_FILE_NAME = "hotcodepush.json";
public static final int DOWNLOAD_BUFFER_SIZE = 1024 * 256;
public static final String DOWNLOAD_FILE_NAME = "download.zip";
public static final String DOWNLOAD_PROGRESS_EVENT_NAME = "CodePushDownloadProgress";
public static final String DOWNLOAD_URL_KEY = "downloadUrl";
public static final String FAILED_UPDATES_KEY = "CODE_PUSH_FAILED_UPDATES";
public static final String PACKAGE_FILE_NAME = "app.json";
public static final String PACKAGE_HASH_KEY = "packageHash";
public static final String PENDING_UPDATE_HASH_KEY = "hash";
public static final String PENDING_UPDATE_IS_LOADING_KEY = "isLoading";
public static final String PENDING_UPDATE_KEY = "CODE_PUSH_PENDING_UPDATE";
public static final String PREVIOUS_PACKAGE_KEY = "previousPackage";
public static final String REACT_NATIVE_LOG_TAG = "ReactNative";
public static final String RELATIVE_BUNDLE_PATH_KEY = "bundlePath";
public static final String STATUS_FILE = "codepush.json";
public static final String UNZIPPED_FOLDER_NAME = "unzipped";
public static final String CODE_PUSH_APK_BUILD_TIME_KEY = "CODE_PUSH_APK_BUILD_TIME";
public static final String BUNDLE_JWT_FILE = ".codepushrelease";
public static final String LATEST_ROLLBACK_INFO_KEY = "LATEST_ROLLBACK_INFO";
public static final String LATEST_ROLLBACK_PACKAGE_HASH_KEY = "packageHash";
public static final String LATEST_ROLLBACK_TIME_KEY = "time";
public static final String LATEST_ROLLBACK_COUNT_KEY = "count";
public static final String CLIENT_UNIQUE_ID_KEY = "clientUniqueId";
}
================================================
FILE: android/app/src/main/java/com/microsoft/codepush/react/CodePushDialog.java
================================================
package com.microsoft.codepush.react;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.LifecycleEventListener;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
public class CodePushDialog extends ReactContextBaseJavaModule{
public CodePushDialog(ReactApplicationContext reactContext) {
super(reactContext);
}
@ReactMethod
public void showDialog(final String title, final String message, final String button1Text,
final String button2Text, final Callback successCallback, Callback errorCallback) {
Activity currentActivity = getCurrentActivity();
if (currentActivity == null || currentActivity.isFinishing()) {
// If getCurrentActivity is null, it could be because the app is backgrounded,
// so we show the dialog when the app resumes)
getReactApplicationContext().addLifecycleEventListener(new LifecycleEventListener() {
@Override
public void onHostResume() {
Activity currentActivity = getCurrentActivity();
if (currentActivity != null) {
getReactApplicationContext().removeLifecycleEventListener(this);
showDialogInternal(title, message, button1Text, button2Text, successCallback, currentActivity);
}
}
@Override
public void onHostPause() {
}
@Override
public void onHostDestroy() {
}
});
} else {
showDialogInternal(title, message, button1Text, button2Text, successCallback, currentActivity);
}
}
private void showDialogInternal(String title, String message, String button1Text,
String button2Text, final Callback successCallback, Activity currentActivity) {
AlertDialog.Builder builder = new AlertDialog.Builder(currentActivity);
builder.setCancelable(false);
DialogInterface.OnClickListener clickListener = new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
try {
dialog.cancel();
switch (which) {
case DialogInterface.BUTTON_POSITIVE:
successCallback.invoke(0);
break;
case DialogInterface.BUTTON_NEGATIVE:
successCallback.invoke(1);
break;
default:
throw new CodePushUnknownException("Unknown button ID pressed.");
}
} catch (Throwable e) {
CodePushUtils.log(e);
}
}
};
if (title != null) {
builder.setTitle(title);
}
if (message != null) {
builder.setMessage(message);
}
if (button1Text != null) {
builder.setPositiveButton(button1Text, clickListener);
}
if (button2Text != null) {
builder.setNegativeButton(button2Text, clickListener);
}
AlertDialog dialog = builder.create();
dialog.show();
}
@Override
public String getName() {
return "CodePushDialog";
}
}
================================================
FILE: android/app/src/main/java/com/microsoft/codepush/react/CodePushInstallMode.java
================================================
package com.microsoft.codepush.react;
public enum CodePushInstallMode {
IMMEDIATE(0),
ON_NEXT_RESTART(1),
ON_NEXT_RESUME(2),
ON_NEXT_SUSPEND(3);
private final int value;
CodePushInstallMode(int value) {
this.value = value;
}
public int getValue() {
return this.value;
}
}
================================================
FILE: android/app/src/main/java/com/microsoft/codepush/react/CodePushInvalidPublicKeyException.java
================================================
package com.microsoft.codepush.react;
class CodePushInvalidPublicKeyException extends RuntimeException {
public CodePushInvalidPublicKeyException(String message, Throwable cause) {
super(message, cause);
}
public CodePushInvalidPublicKeyException(String message) {
super(message);
}
}
================================================
FILE: android/app/src/main/java/com/microsoft/codepush/react/CodePushInvalidUpdateException.java
================================================
package com.microsoft.codepush.react;
public class CodePushInvalidUpdateException extends RuntimeException {
public CodePushInvalidUpdateException(String message) {
super(message);
}
}
================================================
FILE: android/app/src/main/java/com/microsoft/codepush/react/CodePushMalformedDataException.java
================================================
package com.microsoft.codepush.react;
import java.net.MalformedURLException;
public class CodePushMalformedDataException extends RuntimeException {
public CodePushMalformedDataException(String path, Throwable cause) {
super("Unable to parse contents of " + path + ", the file may be corrupted.", cause);
}
public CodePushMalformedDataException(String url, MalformedURLException cause) {
super("The package has an invalid downloadUrl: " + url, cause);
}
}
================================================
FILE: android/app/src/main/java/com/microsoft/codepush/react/CodePushNativeModule.java
================================================
package com.microsoft.codepush.react;
import android.app.Activity;
import android.content.SharedPreferences;
import android.os.AsyncTask;
import android.os.Handler;
import android.os.Looper;
import android.provider.Settings;
import android.view.View;
import com.facebook.react.ReactApplication;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.ReactRootView;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.JSBundleLoader;
import com.facebook.react.bridge.LifecycleEventListener;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.modules.core.ChoreographerCompat;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.facebook.react.modules.core.ReactChoreographer;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
public class CodePushNativeModule extends ReactContextBaseJavaModule {
private String mBinaryContentsHash = null;
private String mClientUniqueId = null;
private LifecycleEventListener mLifecycleEventListener = null;
private int mMinimumBackgroundDuration = 0;
private CodePush mCodePush;
private SettingsManager mSettingsManager;
private CodePushTelemetryManager mTelemetryManager;
private CodePushUpdateManager mUpdateManager;
private boolean _allowed = true;
private boolean _restartInProgress = false;
private ArrayList _restartQueue = new ArrayList<>();
public CodePushNativeModule(ReactApplicationContext reactContext, CodePush codePush, CodePushUpdateManager codePushUpdateManager, CodePushTelemetryManager codePushTelemetryManager, SettingsManager settingsManager) {
super(reactContext);
mCodePush = codePush;
mSettingsManager = settingsManager;
mTelemetryManager = codePushTelemetryManager;
mUpdateManager = codePushUpdateManager;
// Initialize module state while we have a reference to the current context.
mBinaryContentsHash = CodePushUpdateUtils.getHashForBinaryContents(reactContext, mCodePush.isDebugMode());
SharedPreferences preferences = codePush.getContext().getSharedPreferences(CodePushConstants.CODE_PUSH_PREFERENCES, 0);
mClientUniqueId = preferences.getString(CodePushConstants.CLIENT_UNIQUE_ID_KEY, null);
if (mClientUniqueId == null) {
mClientUniqueId = UUID.randomUUID().toString();
preferences.edit().putString(CodePushConstants.CLIENT_UNIQUE_ID_KEY, mClientUniqueId).apply();
}
}
@Override
public Map getConstants() {
final Map constants = new HashMap<>();
constants.put("codePushInstallModeImmediate", CodePushInstallMode.IMMEDIATE.getValue());
constants.put("codePushInstallModeOnNextRestart", CodePushInstallMode.ON_NEXT_RESTART.getValue());
constants.put("codePushInstallModeOnNextResume", CodePushInstallMode.ON_NEXT_RESUME.getValue());
constants.put("codePushInstallModeOnNextSuspend", CodePushInstallMode.ON_NEXT_SUSPEND.getValue());
constants.put("codePushUpdateStateRunning", CodePushUpdateState.RUNNING.getValue());
constants.put("codePushUpdateStatePending", CodePushUpdateState.PENDING.getValue());
constants.put("codePushUpdateStateLatest", CodePushUpdateState.LATEST.getValue());
return constants;
}
@Override
public String getName() {
return "CodePush";
}
private void loadBundleLegacy() {
final Activity currentActivity = getCurrentActivity();
if (currentActivity == null) {
// The currentActivity can be null if it is backgrounded / destroyed, so we simply
// no-op to prevent any null pointer exceptions.
return;
}
mCodePush.invalidateCurrentInstance();
currentActivity.runOnUiThread(new Runnable() {
@Override
public void run() {
currentActivity.recreate();
}
});
}
// Use reflection to find and set the appropriate fields on ReactInstanceManager. See #556 for a proposal for a less brittle way
// to approach this.
private void setJSBundle(ReactInstanceManager instanceManager, String latestJSBundleFile) throws IllegalAccessException {
try {
JSBundleLoader latestJSBundleLoader;
if (latestJSBundleFile.toLowerCase().startsWith("assets://")) {
latestJSBundleLoader = JSBundleLoader.createAssetLoader(getReactApplicationContext(), latestJSBundleFile, false);
} else {
latestJSBundleLoader = JSBundleLoader.createFileLoader(latestJSBundleFile);
}
Field bundleLoaderField = instanceManager.getClass().getDeclaredField("mBundleLoader");
bundleLoaderField.setAccessible(true);
bundleLoaderField.set(instanceManager, latestJSBundleLoader);
} catch (Exception e) {
CodePushUtils.log("Unable to set JSBundle - CodePush may not support this version of React Native");
throw new IllegalAccessException("Could not setJSBundle");
}
}
private void loadBundle() {
clearLifecycleEventListener();
try {
mCodePush.clearDebugCacheIfNeeded(resolveInstanceManager());
} catch(Exception e) {
// If we got error in out reflection we should clear debug cache anyway.
mCodePush.clearDebugCacheIfNeeded(null);
}
try {
// #1) Get the ReactInstanceManager instance, which is what includes the
// logic to reload the current React context.
final ReactInstanceManager instanceManager = resolveInstanceManager();
if (instanceManager == null) {
return;
}
String latestJSBundleFile = mCodePush.getJSBundleFileInternal(mCodePush.getAssetsBundleFileName());
// #2) Update the locally stored JS bundle file path
setJSBundle(instanceManager, latestJSBundleFile);
// #3) Get the context creation method and fire it on the UI thread (which RN enforces)
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
try {
// We don't need to resetReactRootViews anymore
// due the issue https://github.com/facebook/react-native/issues/14533
// has been fixed in RN 0.46.0
//resetReactRootViews(instanceManager);
instanceManager.recreateReactContextInBackground();
mCodePush.initializeUpdateAfterRestart();
} catch (Exception e) {
// The recreation method threw an unknown exception
// so just simply fallback to restarting the Activity (if it exists)
loadBundleLegacy();
}
}
});
} catch (Exception e) {
// Our reflection logic failed somewhere
// so fall back to restarting the Activity (if it exists)
CodePushUtils.log("Failed to load the bundle, falling back to restarting the Activity (if it exists). " + e.getMessage());
loadBundleLegacy();
}
}
// This workaround has been implemented in order to fix https://github.com/facebook/react-native/issues/14533
// resetReactRootViews allows to call recreateReactContextInBackground without any exceptions
// This fix also relates to https://github.com/microsoft/react-native-code-push/issues/878
private void resetReactRootViews(ReactInstanceManager instanceManager) throws NoSuchFieldException, IllegalAccessException {
Field mAttachedRootViewsField = instanceManager.getClass().getDeclaredField("mAttachedRootViews");
mAttachedRootViewsField.setAccessible(true);
List mAttachedRootViews = (List)mAttachedRootViewsField.get(instanceManager);
for (ReactRootView reactRootView : mAttachedRootViews) {
reactRootView.removeAllViews();
reactRootView.setId(View.NO_ID);
}
mAttachedRootViewsField.set(instanceManager, mAttachedRootViews);
}
private void clearLifecycleEventListener() {
// Remove LifecycleEventListener to prevent infinite restart loop
if (mLifecycleEventListener != null) {
getReactApplicationContext().removeLifecycleEventListener(mLifecycleEventListener);
mLifecycleEventListener = null;
}
}
// Use reflection to find the ReactInstanceManager. See #556 for a proposal for a less brittle way to approach this.
private ReactInstanceManager resolveInstanceManager() throws NoSuchFieldException, IllegalAccessException {
ReactInstanceManager instanceManager = CodePush.getReactInstanceManager();
if (instanceManager != null) {
return instanceManager;
}
final Activity currentActivity = getCurrentActivity();
if (currentActivity == null) {
return null;
}
ReactApplication reactApplication = (ReactApplication) currentActivity.getApplication();
instanceManager = reactApplication.getReactNativeHost().getReactInstanceManager();
return instanceManager;
}
private void restartAppInternal(boolean onlyIfUpdateIsPending) {
if (this._restartInProgress) {
CodePushUtils.log("Restart request queued until the current restart is completed");
this._restartQueue.add(onlyIfUpdateIsPending);
return;
} else if (!this._allowed) {
CodePushUtils.log("Restart request queued until restarts are re-allowed");
this._restartQueue.add(onlyIfUpdateIsPending);
return;
}
this._restartInProgress = true;
if (!onlyIfUpdateIsPending || mSettingsManager.isPendingUpdate(null)) {
loadBundle();
CodePushUtils.log("Restarting app");
return;
}
this._restartInProgress = false;
if (this._restartQueue.size() > 0) {
boolean buf = this._restartQueue.get(0);
this._restartQueue.remove(0);
this.restartAppInternal(buf);
}
}
@ReactMethod
public void allow(Promise promise) {
CodePushUtils.log("Re-allowing restarts");
this._allowed = true;
if (_restartQueue.size() > 0) {
CodePushUtils.log("Executing pending restart");
boolean buf = this._restartQueue.get(0);
this._restartQueue.remove(0);
this.restartAppInternal(buf);
}
promise.resolve(null);
return;
}
@ReactMethod
public void clearPendingRestart(Promise promise) {
this._restartQueue.clear();
promise.resolve(null);
return;
}
@ReactMethod
public void disallow(Promise promise) {
CodePushUtils.log("Disallowing restarts");
this._allowed = false;
promise.resolve(null);
return;
}
@ReactMethod
public void restartApp(boolean onlyIfUpdateIsPending, Promise promise) {
try {
restartAppInternal(onlyIfUpdateIsPending);
promise.resolve(null);
} catch(CodePushUnknownException e) {
CodePushUtils.log(e);
promise.reject(e);
}
}
@ReactMethod
public void downloadUpdate(final ReadableMap updatePackage, final boolean notifyProgress, final Promise promise) {
AsyncTask asyncTask = new AsyncTask() {
@Override
protected Void doInBackground(Void... params) {
try {
JSONObject mutableUpdatePackage = CodePushUtils.convertReadableToJsonObject(updatePackage);
CodePushUtils.setJSONValueForKey(mutableUpdatePackage, CodePushConstants.BINARY_MODIFIED_TIME_KEY, "" + mCodePush.getBinaryResourcesModifiedTime());
mUpdateManager.downloadPackage(mutableUpdatePackage, mCodePush.getAssetsBundleFileName(), new DownloadProgressCallback() {
private boolean hasScheduledNextFrame = false;
private DownloadProgress latestDownloadProgress = null;
@Override
public void call(DownloadProgress downloadProgress) {
if (!notifyProgress) {
return;
}
latestDownloadProgress = downloadProgress;
// If the download is completed, synchronously send the last event.
if (latestDownloadProgress.isCompleted()) {
dispatchDownloadProgressEvent();
return;
}
if (hasScheduledNextFrame) {
return;
}
hasScheduledNextFrame = true;
getReactApplicationContext().runOnUiQueueThread(new Runnable() {
@Override
public void run() {
ReactChoreographer.getInstance().postFrameCallback(ReactChoreographer.CallbackType.TIMERS_EVENTS, new ChoreographerCompat.FrameCallback() {
@Override
public void doFrame(long frameTimeNanos) {
if (!latestDownloadProgress.isCompleted()) {
dispatchDownloadProgressEvent();
}
hasScheduledNextFrame = false;
}
});
}
});
}
public void dispatchDownloadProgressEvent() {
getReactApplicationContext()
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit(CodePushConstants.DOWNLOAD_PROGRESS_EVENT_NAME, latestDownloadProgress.createWritableMap());
}
}, mCodePush.getPublicKey());
JSONObject newPackage = mUpdateManager.getPackage(CodePushUtils.tryGetString(updatePackage, CodePushConstants.PACKAGE_HASH_KEY));
promise.resolve(CodePushUtils.convertJsonObjectToWritable(newPackage));
} catch (CodePushInvalidUpdateException e) {
CodePushUtils.log(e);
mSettingsManager.saveFailedUpdate(CodePushUtils.convertReadableToJsonObject(updatePackage));
promise.reject(e);
} catch (IOException | CodePushUnknownException e) {
CodePushUtils.log(e);
promise.reject(e);
}
return null;
}
};
asyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
@ReactMethod
public void getConfiguration(Promise promise) {
try {
WritableMap configMap = Arguments.createMap();
configMap.putString("appVersion", mCodePush.getAppVersion());
configMap.putString("clientUniqueId", mClientUniqueId);
configMap.putString("deploymentKey", mCodePush.getDeploymentKey());
configMap.putString("serverUrl", mCodePush.getServerUrl());
// The binary hash may be null in debug builds
if (mBinaryContentsHash != null) {
configMap.putString(CodePushConstants.PACKAGE_HASH_KEY, mBinaryContentsHash);
}
promise.resolve(configMap);
} catch(CodePushUnknownException e) {
CodePushUtils.log(e);
promise.reject(e);
}
}
@ReactMethod
public void getUpdateMetadata(final int updateState, final Promise promise) {
AsyncTask asyncTask = new AsyncTask() {
@Override
protected Void doInBackground(Void... params) {
try {
JSONObject currentPackage = mUpdateManager.getCurrentPackage();
if (currentPackage == null) {
promise.resolve(null);
return null;
}
Boolean currentUpdateIsPending = false;
if (currentPackage.has(CodePushConstants.PACKAGE_HASH_KEY)) {
String currentHash = currentPackage.optString(CodePushConstants.PACKAGE_HASH_KEY, null);
currentUpdateIsPending = mSettingsManager.isPendingUpdate(currentHash);
}
if (updateState == CodePushUpdateState.PENDING.getValue() && !currentUpdateIsPending) {
// The caller wanted a pending update
// but there isn't currently one.
promise.resolve(null);
} else if (updateState == CodePushUpdateState.RUNNING.getValue() && currentUpdateIsPending) {
// The caller wants the running update, but the current
// one is pending, so we need to grab the previous.
JSONObject previousPackage = mUpdateManager.getPreviousPackage();
if (previousPackage == null) {
promise.resolve(null);
return null;
}
promise.resolve(CodePushUtils.convertJsonObjectToWritable(previousPackage));
} else {
// The current package satisfies the request:
// 1) Caller wanted a pending, and there is a pending update
// 2) Caller wanted the running update, and there isn't a pending
// 3) Caller wants the latest update, regardless if it's pending or not
if (mCodePush.isRunningBinaryVersion()) {
// This only matters in Debug builds. Since we do not clear "outdated" updates,
// we need to indicate to the JS side that somehow we have a current update on
// disk that is not actually running.
CodePushUtils.setJSONValueForKey(currentPackage, "_isDebugOnly", true);
}
// Enable differentiating pending vs. non-pending updates
CodePushUtils.setJSONValueForKey(currentPackage, "isPending", currentUpdateIsPending);
promise.resolve(CodePushUtils.convertJsonObjectToWritable(currentPackage));
}
} catch (CodePushMalformedDataException e) {
// We need to recover the app in case 'codepush.json' is corrupted
CodePushUtils.log(e.getMessage());
clearUpdates();
promise.resolve(null);
} catch(CodePushUnknownException e) {
CodePushUtils.log(e);
promise.reject(e);
}
return null;
}
};
asyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
@ReactMethod
public void getNewStatusReport(final Promise promise) {
AsyncTask asyncTask = new AsyncTask() {
@Override
protected Void doInBackground(Void... params) {
try {
if (mCodePush.needToReportRollback()) {
mCodePush.setNeedToReportRollback(false);
JSONArray failedUpdates = mSettingsManager.getFailedUpdates();
if (failedUpdates != null && failedUpdates.length() > 0) {
try {
JSONObject lastFailedPackageJSON = failedUpdates.getJSONObject(failedUpdates.length() - 1);
WritableMap lastFailedPackage = CodePushUtils.convertJsonObjectToWritable(lastFailedPackageJSON);
WritableMap failedStatusReport = mTelemetryManager.getRollbackReport(lastFailedPackage);
if (failedStatusReport != null) {
promise.resolve(failedStatusReport);
return null;
}
} catch (JSONException e) {
throw new CodePushUnknownException("Unable to read failed updates information stored in SharedPreferences.", e);
}
}
} else if (mCodePush.didUpdate()) {
JSONObject currentPackage = mUpdateManager.getCurrentPackage();
if (currentPackage != null) {
WritableMap newPackageStatusReport = mTelemetryManager.getUpdateReport(CodePushUtils.convertJsonObjectToWritable(currentPackage));
if (newPackageStatusReport != null) {
promise.resolve(newPackageStatusReport);
return null;
}
}
} else if (mCodePush.isRunningBinaryVersion()) {
WritableMap newAppVersionStatusReport = mTelemetryManager.getBinaryUpdateReport(mCodePush.getAppVersion());
if (newAppVersionStatusReport != null) {
promise.resolve(newAppVersionStatusReport);
return null;
}
} else {
WritableMap retryStatusReport = mTelemetryManager.getRetryStatusReport();
if (retryStatusReport != null) {
promise.resolve(retryStatusReport);
return null;
}
}
promise.resolve("");
} catch(CodePushUnknownException e) {
CodePushUtils.log(e);
promise.reject(e);
}
return null;
}
};
asyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
@ReactMethod
public void installUpdate(final ReadableMap updatePackage, final int installMode, final int minimumBackgroundDuration, final Promise promise) {
AsyncTask asyncTask = new AsyncTask() {
@Override
protected Void doInBackground(Void... params) {
try {
mUpdateManager.installPackage(CodePushUtils.convertReadableToJsonObject(updatePackage), mSettingsManager.isPendingUpdate(null));
String pendingHash = CodePushUtils.tryGetString(updatePackage, CodePushConstants.PACKAGE_HASH_KEY);
if (pendingHash == null) {
throw new CodePushUnknownException("Update package to be installed has no hash.");
} else {
mSettingsManager.savePendingUpdate(pendingHash, /* isLoading */false);
}
if (installMode == CodePushInstallMode.ON_NEXT_RESUME.getValue() ||
// We also add the resume listener if the installMode is IMMEDIATE, because
// if the current activity is backgrounded, we want to reload the bundle when
// it comes back into the foreground.
installMode == CodePushInstallMode.IMMEDIATE.getValue() ||
installMode == CodePushInstallMode.ON_NEXT_SUSPEND.getValue()) {
// Store the minimum duration on the native module as an instance
// variable instead of relying on a closure below, so that any
// subsequent resume-based installs could override it.
CodePushNativeModule.this.mMinimumBackgroundDuration = minimumBackgroundDuration;
if (mLifecycleEventListener == null) {
// Ensure we do not add the listener twice.
mLifecycleEventListener = new LifecycleEventListener() {
private Date lastPausedDate = null;
private Handler appSuspendHandler = new Handler(Looper.getMainLooper());
private Runnable loadBundleRunnable = new Runnable() {
@Override
public void run() {
CodePushUtils.log("Loading bundle on suspend");
restartAppInternal(false);
}
};
@Override
public void onHostResume() {
appSuspendHandler.removeCallbacks(loadBundleRunnable);
// As of RN 36, the resume handler fires immediately if the app is in
// the foreground, so explicitly wait for it to be backgrounded first
if (lastPausedDate != null) {
long durationInBackground = (new Date().getTime() - lastPausedDate.getTime()) / 1000;
if (installMode == CodePushInstallMode.IMMEDIATE.getValue()
|| durationInBackground >= CodePushNativeModule.this.mMinimumBackgroundDuration) {
CodePushUtils.log("Loading bundle on resume");
restartAppInternal(false);
}
}
}
@Override
public void onHostPause() {
// Save the current time so that when the app is later
// resumed, we can detect how long it was in the background.
lastPausedDate = new Date();
if (installMode == CodePushInstallMode.ON_NEXT_SUSPEND.getValue() && mSettingsManager.isPendingUpdate(null)) {
appSuspendHandler.postDelayed(loadBundleRunnable, minimumBackgroundDuration * 1000);
}
}
@Override
public void onHostDestroy() {
}
};
getReactApplicationContext().addLifecycleEventListener(mLifecycleEventListener);
}
}
promise.resolve("");
} catch(CodePushUnknownException e) {
CodePushUtils.log(e);
promise.reject(e);
}
return null;
}
};
asyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
@ReactMethod
public void isFailedUpdate(String packageHash, Promise promise) {
try {
promise.resolve(mSettingsManager.isFailedHash(packageHash));
} catch (CodePushUnknownException e) {
CodePushUtils.log(e);
promise.reject(e);
}
}
@ReactMethod
public void getLatestRollbackInfo(Promise promise) {
try {
JSONObject latestRollbackInfo = mSettingsManager.getLatestRollbackInfo();
if (latestRollbackInfo != null) {
promise.resolve(CodePushUtils.convertJsonObjectToWritable(latestRollbackInfo));
} else {
promise.resolve(null);
}
} catch (CodePushUnknownException e) {
CodePushUtils.log(e);
promise.reject(e);
}
}
@ReactMethod
public void setLatestRollbackInfo(String packageHash, Promise promise) {
try {
mSettingsManager.setLatestRollbackInfo(packageHash);
promise.resolve(null);
} catch (CodePushUnknownException e) {
CodePushUtils.log(e);
promise.reject(e);
}
}
@ReactMethod
public void isFirstRun(String packageHash, Promise promise) {
try {
boolean isFirstRun = mCodePush.didUpdate()
&& packageHash != null
&& packageHash.length() > 0
&& packageHash.equals(mUpdateManager.getCurrentPackageHash());
promise.resolve(isFirstRun);
} catch(CodePushUnknownException e) {
CodePushUtils.log(e);
promise.reject(e);
}
}
@ReactMethod
public void notifyApplicationReady(Promise promise) {
try {
mSettingsManager.removePendingUpdate();
promise.resolve("");
} catch(CodePushUnknownException e) {
CodePushUtils.log(e);
promise.reject(e);
}
}
@ReactMethod
public void recordStatusReported(ReadableMap statusReport) {
try {
mTelemetryManager.recordStatusReported(statusReport);
} catch(CodePushUnknownException e) {
CodePushUtils.log(e);
}
}
@ReactMethod
public void saveStatusReportForRetry(ReadableMap statusReport) {
try {
mTelemetryManager.saveStatusReportForRetry(statusReport);
} catch(CodePushUnknownException e) {
CodePushUtils.log(e);
}
}
@ReactMethod
// Replaces the current bundle with the one downloaded from removeBundleUrl.
// It is only to be used during tests. No-ops if the test configuration flag is not set.
public void downloadAndReplaceCurrentBundle(String remoteBundleUrl) {
try {
if (mCodePush.isUsingTestConfiguration()) {
try {
mUpdateManager.downloadAndReplaceCurrentBundle(remoteBundleUrl, mCodePush.getAssetsBundleFileName());
} catch (IOException e) {
throw new CodePushUnknownException("Unable to replace current bundle", e);
}
}
} catch(CodePushUnknownException | CodePushMalformedDataException e) {
CodePushUtils.log(e);
}
}
/**
* This method clears CodePush's downloaded updates.
* It is needed to switch to a different deployment if the current deployment is more recent.
* Note: we don’t recommend to use this method in scenarios other than that (CodePush will call
* this method automatically when needed in other cases) as it could lead to unpredictable
* behavior.
*/
@ReactMethod
public void clearUpdates() {
CodePushUtils.log("Clearing updates.");
mCodePush.clearUpdates();
}
@ReactMethod
public void addListener(String eventName) {
// Set up any upstream listeners or background tasks as necessary
}
@ReactMethod
public void removeListeners(Integer count) {
// Remove upstream listeners, stop unnecessary background tasks
}
}
================================================
FILE: android/app/src/main/java/com/microsoft/codepush/react/CodePushNotInitializedException.java
================================================
package com.microsoft.codepush.react;
public final class CodePushNotInitializedException extends RuntimeException {
public CodePushNotInitializedException(String message, Throwable cause) {
super(message, cause);
}
public CodePushNotInitializedException(String message) {
super(message);
}
}
================================================
FILE: android/app/src/main/java/com/microsoft/codepush/react/CodePushTelemetryManager.java
================================================
package com.microsoft.codepush.react;
import android.content.Context;
import android.content.SharedPreferences;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableMap;
import org.json.JSONException;
import org.json.JSONObject;
public class CodePushTelemetryManager {
private SharedPreferences mSettings;
private final String APP_VERSION_KEY = "appVersion";
private final String DEPLOYMENT_FAILED_STATUS = "DeploymentFailed";
private final String DEPLOYMENT_KEY_KEY = "deploymentKey";
private final String DEPLOYMENT_SUCCEEDED_STATUS = "DeploymentSucceeded";
private final String LABEL_KEY = "label";
private final String LAST_DEPLOYMENT_REPORT_KEY = "CODE_PUSH_LAST_DEPLOYMENT_REPORT";
private final String PACKAGE_KEY = "package";
private final String PREVIOUS_DEPLOYMENT_KEY_KEY = "previousDeploymentKey";
private final String PREVIOUS_LABEL_OR_APP_VERSION_KEY = "previousLabelOrAppVersion";
private final String RETRY_DEPLOYMENT_REPORT_KEY = "CODE_PUSH_RETRY_DEPLOYMENT_REPORT";
private final String STATUS_KEY = "status";
public CodePushTelemetryManager(Context applicationContext) {
mSettings = applicationContext.getSharedPreferences(CodePushConstants.CODE_PUSH_PREFERENCES, 0);
}
public WritableMap getBinaryUpdateReport(String appVersion) {
String previousStatusReportIdentifier = this.getPreviousStatusReportIdentifier();
WritableMap reportMap = null;
if (previousStatusReportIdentifier == null) {
this.clearRetryStatusReport();
reportMap = Arguments.createMap();
reportMap.putString(APP_VERSION_KEY, appVersion);
} else if (!previousStatusReportIdentifier.equals(appVersion)) {
this.clearRetryStatusReport();
reportMap = Arguments.createMap();
if (this.isStatusReportIdentifierCodePushLabel(previousStatusReportIdentifier)) {
String previousDeploymentKey = this.getDeploymentKeyFromStatusReportIdentifier(previousStatusReportIdentifier);
String previousLabel = this.getVersionLabelFromStatusReportIdentifier(previousStatusReportIdentifier);
reportMap.putString(APP_VERSION_KEY, appVersion);
reportMap.putString(PREVIOUS_DEPLOYMENT_KEY_KEY, previousDeploymentKey);
reportMap.putString(PREVIOUS_LABEL_OR_APP_VERSION_KEY, previousLabel);
} else {
// Previous status report was with a binary app version.
reportMap.putString(APP_VERSION_KEY, appVersion);
reportMap.putString(PREVIOUS_LABEL_OR_APP_VERSION_KEY, previousStatusReportIdentifier);
}
}
return reportMap;
}
public WritableMap getRetryStatusReport() {
String retryStatusReportString = mSettings.getString(RETRY_DEPLOYMENT_REPORT_KEY, null);
if (retryStatusReportString != null) {
clearRetryStatusReport();
try {
JSONObject retryStatusReport = new JSONObject(retryStatusReportString);
return CodePushUtils.convertJsonObjectToWritable(retryStatusReport);
} catch (JSONException e) {
e.printStackTrace();
}
}
return null;
}
public WritableMap getRollbackReport(WritableMap lastFailedPackage) {
WritableMap reportMap = Arguments.createMap();
reportMap.putMap(PACKAGE_KEY, lastFailedPackage);
reportMap.putString(STATUS_KEY, DEPLOYMENT_FAILED_STATUS);
return reportMap;
}
public WritableMap getUpdateReport(WritableMap currentPackage) {
String currentPackageIdentifier = this.getPackageStatusReportIdentifier(currentPackage);
String previousStatusReportIdentifier = this.getPreviousStatusReportIdentifier();
WritableMap reportMap = null;
if (currentPackageIdentifier != null) {
if (previousStatusReportIdentifier == null) {
this.clearRetryStatusReport();
reportMap = Arguments.createMap();
reportMap.putMap(PACKAGE_KEY, currentPackage);
reportMap.putString(STATUS_KEY, DEPLOYMENT_SUCCEEDED_STATUS);
} else if (!previousStatusReportIdentifier.equals(currentPackageIdentifier)) {
this.clearRetryStatusReport();
reportMap = Arguments.createMap();
if (this.isStatusReportIdentifierCodePushLabel(previousStatusReportIdentifier)) {
String previousDeploymentKey = this.getDeploymentKeyFromStatusReportIdentifier(previousStatusReportIdentifier);
String previousLabel = this.getVersionLabelFromStatusReportIdentifier(previousStatusReportIdentifier);
reportMap.putMap(PACKAGE_KEY, currentPackage);
reportMap.putString(STATUS_KEY, DEPLOYMENT_SUCCEEDED_STATUS);
reportMap.putString(PREVIOUS_DEPLOYMENT_KEY_KEY, previousDeploymentKey);
reportMap.putString(PREVIOUS_LABEL_OR_APP_VERSION_KEY, previousLabel);
} else {
// Previous status report was with a binary app version.
reportMap.putMap(PACKAGE_KEY, currentPackage);
reportMap.putString(STATUS_KEY, DEPLOYMENT_SUCCEEDED_STATUS);
reportMap.putString(PREVIOUS_LABEL_OR_APP_VERSION_KEY, previousStatusReportIdentifier);
}
}
}
return reportMap;
}
public void recordStatusReported(ReadableMap statusReport) {
// We don't need to record rollback reports, so exit early if that's what was specified.
if (statusReport.hasKey(STATUS_KEY) && DEPLOYMENT_FAILED_STATUS.equals(statusReport.getString(STATUS_KEY))) {
return;
}
if (statusReport.hasKey(APP_VERSION_KEY)) {
saveStatusReportedForIdentifier(statusReport.getString(APP_VERSION_KEY));
} else if (statusReport.hasKey(PACKAGE_KEY)) {
String packageIdentifier = getPackageStatusReportIdentifier(statusReport.getMap(PACKAGE_KEY));
saveStatusReportedForIdentifier(packageIdentifier);
}
}
public void saveStatusReportForRetry(ReadableMap statusReport) {
JSONObject statusReportJSON = CodePushUtils.convertReadableToJsonObject(statusReport);
mSettings.edit().putString(RETRY_DEPLOYMENT_REPORT_KEY, statusReportJSON.toString()).commit();
}
private void clearRetryStatusReport() {
mSettings.edit().remove(RETRY_DEPLOYMENT_REPORT_KEY).commit();
}
private String getDeploymentKeyFromStatusReportIdentifier(String statusReportIdentifier) {
String[] parsedIdentifier = statusReportIdentifier.split(":");
if (parsedIdentifier.length > 0) {
return parsedIdentifier[0];
} else {
return null;
}
}
private String getPackageStatusReportIdentifier(ReadableMap updatePackage) {
// Because deploymentKeys can be dynamically switched, we use a
// combination of the deploymentKey and label as the packageIdentifier.
String deploymentKey = CodePushUtils.tryGetString(updatePackage, DEPLOYMENT_KEY_KEY);
String label = CodePushUtils.tryGetString(updatePackage, LABEL_KEY);
if (deploymentKey != null && label != null) {
return deploymentKey + ":" + label;
} else {
return null;
}
}
private String getPreviousStatusReportIdentifier() {
return mSettings.getString(LAST_DEPLOYMENT_REPORT_KEY, null);
}
private String getVersionLabelFromStatusReportIdentifier(String statusReportIdentifier) {
String[] parsedIdentifier = statusReportIdentifier.split(":");
if (parsedIdentifier.length > 1) {
return parsedIdentifier[1];
} else {
return null;
}
}
private boolean isStatusReportIdentifierCodePushLabel(String statusReportIdentifier) {
return statusReportIdentifier != null && statusReportIdentifier.contains(":");
}
private void saveStatusReportedForIdentifier(String appVersionOrPackageIdentifier) {
mSettings.edit().putString(LAST_DEPLOYMENT_REPORT_KEY, appVersionOrPackageIdentifier).commit();
}
}
================================================
FILE: android/app/src/main/java/com/microsoft/codepush/react/CodePushUnknownException.java
================================================
package com.microsoft.codepush.react;
class CodePushUnknownException extends RuntimeException {
public CodePushUnknownException(String message, Throwable cause) {
super(message, cause);
}
public CodePushUnknownException(String message) {
super(message);
}
}
================================================
FILE: android/app/src/main/java/com/microsoft/codepush/react/CodePushUpdateManager.java
================================================
package com.microsoft.codepush.react;
import android.os.Build;
import org.json.JSONObject;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.ByteBuffer;
import javax.net.ssl.HttpsURLConnection;
public class CodePushUpdateManager {
private String mDocumentsDirectory;
public CodePushUpdateManager(String documentsDirectory) {
mDocumentsDirectory = documentsDirectory;
}
private String getDownloadFilePath() {
return CodePushUtils.appendPathComponent(getCodePushPath(), CodePushConstants.DOWNLOAD_FILE_NAME);
}
private String getUnzippedFolderPath() {
return CodePushUtils.appendPathComponent(getCodePushPath(), CodePushConstants.UNZIPPED_FOLDER_NAME);
}
private String getDocumentsDirectory() {
return mDocumentsDirectory;
}
private String getCodePushPath() {
String codePushPath = CodePushUtils.appendPathComponent(getDocumentsDirectory(), CodePushConstants.CODE_PUSH_FOLDER_PREFIX);
if (CodePush.isUsingTestConfiguration()) {
codePushPath = CodePushUtils.appendPathComponent(codePushPath, "TestPackages");
}
return codePushPath;
}
private String getStatusFilePath() {
return CodePushUtils.appendPathComponent(getCodePushPath(), CodePushConstants.STATUS_FILE);
}
public JSONObject getCurrentPackageInfo() {
String statusFilePath = getStatusFilePath();
if (!FileUtils.fileAtPathExists(statusFilePath)) {
return new JSONObject();
}
try {
return CodePushUtils.getJsonObjectFromFile(statusFilePath);
} catch (IOException e) {
// Should not happen.
throw new CodePushUnknownException("Error getting current package info", e);
}
}
public void updateCurrentPackageInfo(JSONObject packageInfo) {
try {
CodePushUtils.writeJsonToFile(packageInfo, getStatusFilePath());
} catch (IOException e) {
// Should not happen.
throw new CodePushUnknownException("Error updating current package info", e);
}
}
public String getCurrentPackageFolderPath() {
JSONObject info = getCurrentPackageInfo();
String packageHash = info.optString(CodePushConstants.CURRENT_PACKAGE_KEY, null);
if (packageHash == null) {
return null;
}
return getPackageFolderPath(packageHash);
}
public String getCurrentPackageBundlePath(String bundleFileName) {
String packageFolder = getCurrentPackageFolderPath();
if (packageFolder == null) {
return null;
}
JSONObject currentPackage = getCurrentPackage();
if (currentPackage == null) {
return null;
}
String relativeBundlePath = currentPackage.optString(CodePushConstants.RELATIVE_BUNDLE_PATH_KEY, null);
if (relativeBundlePath == null) {
return CodePushUtils.appendPathComponent(packageFolder, bundleFileName);
} else {
return CodePushUtils.appendPathComponent(packageFolder, relativeBundlePath);
}
}
public String getPackageFolderPath(String packageHash) {
return CodePushUtils.appendPathComponent(getCodePushPath(), packageHash);
}
public String getCurrentPackageHash() {
JSONObject info = getCurrentPackageInfo();
return info.optString(CodePushConstants.CURRENT_PACKAGE_KEY, null);
}
public String getPreviousPackageHash() {
JSONObject info = getCurrentPackageInfo();
return info.optString(CodePushConstants.PREVIOUS_PACKAGE_KEY, null);
}
public JSONObject getCurrentPackage() {
String packageHash = getCurrentPackageHash();
if (packageHash == null) {
return null;
}
return getPackage(packageHash);
}
public JSONObject getPreviousPackage() {
String packageHash = getPreviousPackageHash();
if (packageHash == null) {
return null;
}
return getPackage(packageHash);
}
public JSONObject getPackage(String packageHash) {
String folderPath = getPackageFolderPath(packageHash);
String packageFilePath = CodePushUtils.appendPathComponent(folderPath, CodePushConstants.PACKAGE_FILE_NAME);
try {
return CodePushUtils.getJsonObjectFromFile(packageFilePath);
} catch (IOException e) {
return null;
}
}
public void downloadPackage(JSONObject updatePackage, String expectedBundleFileName,
DownloadProgressCallback progressCallback,
String stringPublicKey) throws IOException {
String newUpdateHash = updatePackage.optString(CodePushConstants.PACKAGE_HASH_KEY, null);
String newUpdateFolderPath = getPackageFolderPath(newUpdateHash);
String newUpdateMetadataPath = CodePushUtils.appendPathComponent(newUpdateFolderPath, CodePushConstants.PACKAGE_FILE_NAME);
if (FileUtils.fileAtPathExists(newUpdateFolderPath)) {
// This removes any stale data in newPackageFolderPath that could have been left
// uncleared due to a crash or error during the download or install process.
FileUtils.deleteDirectoryAtPath(newUpdateFolderPath);
}
String downloadUrlString = updatePackage.optString(CodePushConstants.DOWNLOAD_URL_KEY, null);
HttpURLConnection connection = null;
BufferedInputStream bin = null;
FileOutputStream fos = null;
BufferedOutputStream bout = null;
File downloadFile = null;
boolean isZip = false;
// Download the file while checking if it is a zip and notifying client of progress.
try {
URL downloadUrl = new URL(downloadUrlString);
connection = (HttpURLConnection) (downloadUrl.openConnection());
if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP &&
downloadUrl.toString().startsWith("https")) {
try {
((HttpsURLConnection)connection).setSSLSocketFactory(new TLSSocketFactory());
} catch (Exception e) {
throw new CodePushUnknownException("Error set SSLSocketFactory. ", e);
}
}
connection.setRequestProperty("Accept-Encoding", "identity");
bin = new BufferedInputStream(connection.getInputStream());
long totalBytes = connection.getContentLength();
long receivedBytes = 0;
File downloadFolder = new File(getCodePushPath());
downloadFolder.mkdirs();
downloadFile = new File(downloadFolder, CodePushConstants.DOWNLOAD_FILE_NAME);
fos = new FileOutputStream(downloadFile);
bout = new BufferedOutputStream(fos, CodePushConstants.DOWNLOAD_BUFFER_SIZE);
byte[] data = new byte[CodePushConstants.DOWNLOAD_BUFFER_SIZE];
byte[] header = new byte[4];
int numBytesRead = 0;
while ((numBytesRead = bin.read(data, 0, CodePushConstants.DOWNLOAD_BUFFER_SIZE)) >= 0) {
if (receivedBytes < 4) {
for (int i = 0; i < numBytesRead; i++) {
int headerOffset = (int) (receivedBytes) + i;
if (headerOffset >= 4) {
break;
}
header[headerOffset] = data[i];
}
}
receivedBytes += numBytesRead;
bout.write(data, 0, numBytesRead);
progressCallback.call(new DownloadProgress(totalBytes, receivedBytes));
}
if (totalBytes != receivedBytes) {
throw new CodePushUnknownException("Received " + receivedBytes + " bytes, expected " + totalBytes);
}
isZip = ByteBuffer.wrap(header).getInt() == 0x504b0304;
} catch (MalformedURLException e) {
throw new CodePushMalformedDataException(downloadUrlString, e);
} finally {
try {
if (bout != null) bout.close();
if (fos != null) fos.close();
if (bin != null) bin.close();
if (connection != null) connection.disconnect();
} catch (IOException e) {
throw new CodePushUnknownException("Error closing IO resources.", e);
}
}
if (isZip) {
// Unzip the downloaded file and then delete the zip
String unzippedFolderPath = getUnzippedFolderPath();
FileUtils.unzipFile(downloadFile, unzippedFolderPath);
FileUtils.deleteFileOrFolderSilently(downloadFile);
// Merge contents with current update based on the manifest
String diffManifestFilePath = CodePushUtils.appendPathComponent(unzippedFolderPath,
CodePushConstants.DIFF_MANIFEST_FILE_NAME);
boolean isDiffUpdate = FileUtils.fileAtPathExists(diffManifestFilePath);
if (isDiffUpdate) {
String currentPackageFolderPath = getCurrentPackageFolderPath();
CodePushUpdateUtils.copyNecessaryFilesFromCurrentPackage(diffManifestFilePath, currentPackageFolderPath, newUpdateFolderPath);
File diffManifestFile = new File(diffManifestFilePath);
diffManifestFile.delete();
}
FileUtils.copyDirectoryContents(unzippedFolderPath, newUpdateFolderPath);
FileUtils.deleteFileAtPathSilently(unzippedFolderPath);
// For zip updates, we need to find the relative path to the jsBundle and save it in the
// metadata so that we can find and run it easily the next time.
String relativeBundlePath = CodePushUpdateUtils.findJSBundleInUpdateContents(newUpdateFolderPath, expectedBundleFileName);
if (relativeBundlePath == null) {
throw new CodePushInvalidUpdateException("Update is invalid - A JS bundle file named \"" + expectedBundleFileName + "\" could not be found within the downloaded contents. Please check that you are releasing your CodePush updates using the exact same JS bundle file name that was shipped with your app's binary.");
} else {
if (FileUtils.fileAtPathExists(newUpdateMetadataPath)) {
File metadataFileFromOldUpdate = new File(newUpdateMetadataPath);
metadataFileFromOldUpdate.delete();
}
if (isDiffUpdate) {
CodePushUtils.log("Applying diff update.");
} else {
CodePushUtils.log("Applying full update.");
}
boolean isSignatureVerificationEnabled = (stringPublicKey != null);
String signaturePath = CodePushUpdateUtils.getSignatureFilePath(newUpdateFolderPath);
boolean isSignatureAppearedInBundle = FileUtils.fileAtPathExists(signaturePath);
if (isSignatureVerificationEnabled) {
if (isSignatureAppearedInBundle) {
CodePushUpdateUtils.verifyFolderHash(newUpdateFolderPath, newUpdateHash);
CodePushUpdateUtils.verifyUpdateSignature(newUpdateFolderPath, newUpdateHash, stringPublicKey);
} else {
throw new CodePushInvalidUpdateException(
"Error! Public key was provided but there is no JWT signature within app bundle to verify. " +
"Possible reasons, why that might happen: \n" +
"1. You've been released CodePush bundle update using version of CodePush CLI that is not support code signing.\n" +
"2. You've been released CodePush bundle update without providing --privateKeyPath option."
);
}
} else {
if (isSignatureAppearedInBundle) {
CodePushUtils.log(
"Warning! JWT signature exists in codepush update but code integrity check couldn't be performed because there is no public key configured. " +
"Please ensure that public key is properly configured within your application."
);
CodePushUpdateUtils.verifyFolderHash(newUpdateFolderPath, newUpdateHash);
} else {
if (isDiffUpdate) {
CodePushUpdateUtils.verifyFolderHash(newUpdateFolderPath, newUpdateHash);
}
}
}
CodePushUtils.setJSONValueForKey(updatePackage, CodePushConstants.RELATIVE_BUNDLE_PATH_KEY, relativeBundlePath);
}
} else {
// File is a jsbundle, move it to a folder with the packageHash as its name
FileUtils.moveFile(downloadFile, newUpdateFolderPath, expectedBundleFileName);
}
// Save metadata to the folder.
CodePushUtils.writeJsonToFile(updatePackage, newUpdateMetadataPath);
}
public void installPackage(JSONObject updatePackage, boolean removePendingUpdate) {
String packageHash = updatePackage.optString(CodePushConstants.PACKAGE_HASH_KEY, null);
JSONObject info = getCurrentPackageInfo();
String currentPackageHash = info.optString(CodePushConstants.CURRENT_PACKAGE_KEY, null);
if (packageHash != null && packageHash.equals(currentPackageHash)) {
// The current package is already the one being installed, so we should no-op.
return;
}
if (removePendingUpdate) {
String currentPackageFolderPath = getCurrentPackageFolderPath();
if (currentPackageFolderPath != null) {
FileUtils.deleteDirectoryAtPath(currentPackageFolderPath);
}
} else {
String previousPackageHash = getPreviousPackageHash();
if (previousPackageHash != null && !previousPackageHash.equals(packageHash)) {
FileUtils.deleteDirectoryAtPath(getPackageFolderPath(previousPackageHash));
}
CodePushUtils.setJSONValueForKey(info, CodePushConstants.PREVIOUS_PACKAGE_KEY, info.optString(CodePushConstants.CURRENT_PACKAGE_KEY, null));
}
CodePushUtils.setJSONValueForKey(info, CodePushConstants.CURRENT_PACKAGE_KEY, packageHash);
updateCurrentPackageInfo(info);
}
public void rollbackPackage() {
JSONObject info = getCurrentPackageInfo();
String currentPackageFolderPath = getCurrentPackageFolderPath();
FileUtils.deleteDirectoryAtPath(currentPackageFolderPath);
CodePushUtils.setJSONValueForKey(info, CodePushConstants.CURRENT_PACKAGE_KEY, info.optString(CodePushConstants.PREVIOUS_PACKAGE_KEY, null));
CodePushUtils.setJSONValueForKey(info, CodePushConstants.PREVIOUS_PACKAGE_KEY, null);
updateCurrentPackageInfo(info);
}
public void downloadAndReplaceCurrentBundle(String remoteBundleUrl, String bundleFileName) throws IOException {
URL downloadUrl;
HttpURLConnection connection = null;
BufferedInputStream bin = null;
FileOutputStream fos = null;
BufferedOutputStream bout = null;
try {
downloadUrl = new URL(remoteBundleUrl);
connection = (HttpURLConnection) (downloadUrl.openConnection());
bin = new BufferedInputStream(connection.getInputStream());
File downloadFile = new File(getCurrentPackageBundlePath(bundleFileName));
downloadFile.delete();
fos = new FileOutputStream(downloadFile);
bout = new BufferedOutputStream(fos, CodePushConstants.DOWNLOAD_BUFFER_SIZE);
byte[] data = new byte[CodePushConstants.DOWNLOAD_BUFFER_SIZE];
int numBytesRead = 0;
while ((numBytesRead = bin.read(data, 0, CodePushConstants.DOWNLOAD_BUFFER_SIZE)) >= 0) {
bout.write(data, 0, numBytesRead);
}
} catch (MalformedURLException e) {
throw new CodePushMalformedDataException(remoteBundleUrl, e);
} finally {
try {
if (bout != null) bout.close();
if (fos != null) fos.close();
if (bin != null) bin.close();
if (connection != null) connection.disconnect();
} catch (IOException e) {
throw new CodePushUnknownException("Error closing IO resources.", e);
}
}
}
public void clearUpdates() {
FileUtils.deleteDirectoryAtPath(getCodePushPath());
}
}
================================================
FILE: android/app/src/main/java/com/microsoft/codepush/react/CodePushUpdateState.java
================================================
package com.microsoft.codepush.react;
public enum CodePushUpdateState {
RUNNING(0),
PENDING(1),
LATEST(2);
private final int value;
CodePushUpdateState(int value) {
this.value = value;
}
public int getValue() {
return this.value;
}
}
================================================
FILE: android/app/src/main/java/com/microsoft/codepush/react/CodePushUpdateUtils.java
================================================
package com.microsoft.codepush.react;
import android.content.Context;
import android.util.Base64;
import com.nimbusds.jose.JWSVerifier;
import com.nimbusds.jose.crypto.RSASSAVerifier;
import com.nimbusds.jwt.SignedJWT;
import java.security.interfaces.*;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.security.DigestInputStream;
import java.security.KeyFactory;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.spec.X509EncodedKeySpec;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Map;
public class CodePushUpdateUtils {
public static final String NEW_LINE = System.getProperty("line.separator");
// Note: The hashing logic here must mirror the hashing logic in other native SDK's, as well as in the
// CLI. Ensure that any changes here are propagated to these other locations.
public static boolean isHashIgnored(String relativeFilePath) {
final String __MACOSX = "__MACOSX/";
final String DS_STORE = ".DS_Store";
final String CODEPUSH_METADATA = ".codepushrelease";
return relativeFilePath.startsWith(__MACOSX)
|| relativeFilePath.equals(DS_STORE)
|| relativeFilePath.endsWith("/" + DS_STORE)
|| relativeFilePath.equals(CODEPUSH_METADATA)
|| relativeFilePath.endsWith("/" + CODEPUSH_METADATA);
}
private static void addContentsOfFolderToManifest(String folderPath, String pathPrefix, ArrayList manifest) {
File folder = new File(folderPath);
File[] folderFiles = folder.listFiles();
for (File file : folderFiles) {
String fileName = file.getName();
String fullFilePath = file.getAbsolutePath();
String relativePath = (pathPrefix.isEmpty() ? "" : (pathPrefix + "/")) + fileName;
if (CodePushUpdateUtils.isHashIgnored(relativePath)) {
continue;
}
if (file.isDirectory()) {
addContentsOfFolderToManifest(fullFilePath, relativePath, manifest);
} else {
try {
manifest.add(relativePath + ":" + computeHash(new FileInputStream(file)));
} catch (FileNotFoundException e) {
// Should not happen.
throw new CodePushUnknownException("Unable to compute hash of update contents.", e);
}
}
}
}
private static String computeHash(InputStream dataStream) {
MessageDigest messageDigest = null;
DigestInputStream digestInputStream = null;
try {
messageDigest = MessageDigest.getInstance("SHA-256");
digestInputStream = new DigestInputStream(dataStream, messageDigest);
byte[] byteBuffer = new byte[1024 * 8];
while (digestInputStream.read(byteBuffer) != -1) ;
} catch (NoSuchAlgorithmException | IOException e) {
// Should not happen.
throw new CodePushUnknownException("Unable to compute hash of update contents.", e);
} finally {
try {
if (digestInputStream != null) {
digestInputStream.close();
}
if (dataStream != null) {
dataStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
byte[] hash = messageDigest.digest();
return String.format("%064x", new java.math.BigInteger(1, hash));
}
public static void copyNecessaryFilesFromCurrentPackage(String diffManifestFilePath, String currentPackageFolderPath, String newPackageFolderPath) throws IOException {
if (currentPackageFolderPath == null || !new File(currentPackageFolderPath).exists()) {
CodePushUtils.log("Unable to copy files from current package during diff update, because currentPackageFolderPath is invalid.");
return;
}
FileUtils.copyDirectoryContents(currentPackageFolderPath, newPackageFolderPath);
JSONObject diffManifest = CodePushUtils.getJsonObjectFromFile(diffManifestFilePath);
try {
JSONArray deletedFiles = diffManifest.getJSONArray("deletedFiles");
for (int i = 0; i < deletedFiles.length(); i++) {
String fileNameToDelete = deletedFiles.getString(i);
File fileToDelete = new File(newPackageFolderPath, fileNameToDelete);
if (fileToDelete.exists()) {
fileToDelete.delete();
}
}
} catch (JSONException e) {
throw new CodePushUnknownException("Unable to copy files from current package during diff update", e);
}
}
public static String findJSBundleInUpdateContents(String folderPath, String expectedFileName) {
File folder = new File(folderPath);
File[] folderFiles = folder.listFiles();
for (File file : folderFiles) {
String fullFilePath = CodePushUtils.appendPathComponent(folderPath, file.getName());
if (file.isDirectory()) {
String mainBundlePathInSubFolder = findJSBundleInUpdateContents(fullFilePath, expectedFileName);
if (mainBundlePathInSubFolder != null) {
return CodePushUtils.appendPathComponent(file.getName(), mainBundlePathInSubFolder);
}
} else {
String fileName = file.getName();
if (fileName.equals(expectedFileName)) {
return fileName;
}
}
}
return null;
}
public static String getHashForBinaryContents(Context context, boolean isDebugMode) {
try {
return CodePushUtils.getStringFromInputStream(context.getAssets().open(CodePushConstants.CODE_PUSH_HASH_FILE_NAME));
} catch (IOException e) {
try {
return CodePushUtils.getStringFromInputStream(context.getAssets().open(CodePushConstants.CODE_PUSH_OLD_HASH_FILE_NAME));
} catch (IOException ex) {
if (!isDebugMode) {
// Only print this message in "Release" mode. In "Debug", we may not have the
// hash if the build skips bundling the files.
CodePushUtils.log("Unable to get the hash of the binary's bundled resources - \"codepush.gradle\" may have not been added to the build definition.");
}
}
return null;
}
}
// Hashing algorithm:
// 1. Recursively generate a sorted array of format :
// 2. JSON stringify the array
// 3. SHA256-hash the result
public static void verifyFolderHash(String folderPath, String expectedHash) {
CodePushUtils.log("Verifying hash for folder path: " + folderPath);
ArrayList updateContentsManifest = new ArrayList<>();
addContentsOfFolderToManifest(folderPath, "", updateContentsManifest);
//sort manifest strings to make sure, that they are completely equal with manifest strings has been generated in cli!
Collections.sort(updateContentsManifest);
JSONArray updateContentsJSONArray = new JSONArray();
for (String manifestEntry : updateContentsManifest) {
updateContentsJSONArray.put(manifestEntry);
}
// The JSON serialization turns path separators into "\/", e.g. "CodePush\/assets\/image.png"
String updateContentsManifestString = updateContentsJSONArray.toString().replace("\\/", "/");
CodePushUtils.log("Manifest string: " + updateContentsManifestString);
String updateContentsManifestHash = computeHash(new ByteArrayInputStream(updateContentsManifestString.getBytes()));
CodePushUtils.log("Expected hash: " + expectedHash + ", actual hash: " + updateContentsManifestHash);
if (!expectedHash.equals(updateContentsManifestHash)) {
throw new CodePushInvalidUpdateException("The update contents failed the data integrity check.");
}
CodePushUtils.log("The update contents succeeded the data integrity check.");
}
public static Map verifyAndDecodeJWT(String jwt, PublicKey publicKey) {
try {
SignedJWT signedJWT = SignedJWT.parse(jwt);
JWSVerifier verifier = new RSASSAVerifier((RSAPublicKey) publicKey);
if (signedJWT.verify(verifier)) {
Map claims = signedJWT.getJWTClaimsSet().getClaims();
CodePushUtils.log("JWT verification succeeded, payload content: " + claims.toString());
return claims;
}
return null;
} catch (Exception ex) {
CodePushUtils.log(ex.getMessage());
CodePushUtils.log(ex.getStackTrace().toString());
return null;
}
}
public static PublicKey parsePublicKey(String stringPublicKey) {
try {
//remove unnecessary "begin/end public key" entries from string
stringPublicKey = stringPublicKey
.replace("-----BEGIN PUBLIC KEY-----", "")
.replace("-----END PUBLIC KEY-----", "")
.replace(NEW_LINE, "");
byte[] byteKey = Base64.decode(stringPublicKey.getBytes(), Base64.DEFAULT);
X509EncodedKeySpec X509Key = new X509EncodedKeySpec(byteKey);
KeyFactory kf = KeyFactory.getInstance("RSA");
return kf.generatePublic(X509Key);
} catch (Exception e) {
CodePushUtils.log(e.getMessage());
CodePushUtils.log(e.getStackTrace().toString());
return null;
}
}
public static String getSignatureFilePath(String updateFolderPath) {
return CodePushUtils.appendPathComponent(
CodePushUtils.appendPathComponent(updateFolderPath, CodePushConstants.CODE_PUSH_FOLDER_PREFIX),
CodePushConstants.BUNDLE_JWT_FILE
);
}
public static String getSignature(String folderPath) {
final String signatureFilePath = getSignatureFilePath(folderPath);
try {
return FileUtils.readFileToString(signatureFilePath);
} catch (IOException e) {
CodePushUtils.log(e.getMessage());
CodePushUtils.log(e.getStackTrace().toString());
return null;
}
}
public static void verifyUpdateSignature(String folderPath, String packageHash, String stringPublicKey) throws CodePushInvalidUpdateException {
CodePushUtils.log("Verifying signature for folder path: " + folderPath);
final PublicKey publicKey = parsePublicKey(stringPublicKey);
if (publicKey == null) {
throw new CodePushInvalidUpdateException("The update could not be verified because no public key was found.");
}
final String signature = getSignature(folderPath);
if (signature == null) {
throw new CodePushInvalidUpdateException("The update could not be verified because no signature was found.");
}
final Map claims = verifyAndDecodeJWT(signature, publicKey);
if (claims == null) {
throw new CodePushInvalidUpdateException("The update could not be verified because it was not signed by a trusted party.");
}
final String contentHash = (String) claims.get("contentHash");
if (contentHash == null) {
throw new CodePushInvalidUpdateException("The update could not be verified because the signature did not specify a content hash.");
}
if (!contentHash.equals(packageHash)) {
throw new CodePushInvalidUpdateException("The update contents failed the code signing check.");
}
CodePushUtils.log("The update contents succeeded the code signing check.");
}
}
================================================
FILE: android/app/src/main/java/com/microsoft/codepush/react/CodePushUtils.java
================================================
package com.microsoft.codepush.react;
import android.util.Log;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.NoSuchKeyException;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.ReadableMapKeySetIterator;
import com.facebook.react.bridge.ReadableType;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Iterator;
public class CodePushUtils {
public static String appendPathComponent(String basePath, String appendPathComponent) {
return new File(basePath, appendPathComponent).getAbsolutePath();
}
public static WritableArray convertJsonArrayToWritable(JSONArray jsonArr) {
WritableArray arr = Arguments.createArray();
for (int i=0; i it = jsonObj.keys();
while(it.hasNext()){
String key = it.next();
Object obj = null;
try {
if (!jsonObj.isNull(key)) {
obj = jsonObj.get(key);
}
} catch (JSONException jsonException) {
// Should not happen.
throw new CodePushUnknownException("Key " + key + " should exist in " + jsonObj.toString() + ".", jsonException);
}
if (obj instanceof JSONObject)
map.putMap(key, convertJsonObjectToWritable((JSONObject) obj));
else if (obj instanceof JSONArray)
map.putArray(key, convertJsonArrayToWritable((JSONArray) obj));
else if (obj instanceof String)
map.putString(key, (String) obj);
else if (obj instanceof Double)
map.putDouble(key, (Double) obj);
else if (obj instanceof Long)
map.putDouble(key, ((Long) obj).doubleValue());
else if (obj instanceof Integer)
map.putInt(key, (Integer) obj);
else if (obj instanceof Boolean)
map.putBoolean(key, (Boolean) obj);
else if (obj == null)
map.putNull(key);
else
throw new CodePushUnknownException("Unrecognized object: " + obj);
}
return map;
}
public static JSONArray convertReadableToJsonArray(ReadableArray arr) {
JSONArray jsonArr = new JSONArray();
for (int i=0; i 0) {
destStream.write(buffer, 0, bytesRead);
}
} finally {
try {
if (fromFileStream != null) fromFileStream.close();
if (fromBufferedStream != null) fromBufferedStream.close();
if (destStream != null) destStream.close();
} catch (IOException e) {
throw new CodePushUnknownException("Error closing IO resources.", e);
}
}
}
}
}
public static void deleteDirectoryAtPath(String directoryPath) {
if (directoryPath == null) {
CodePushUtils.log("deleteDirectoryAtPath attempted with null directoryPath");
return;
}
File file = new File(directoryPath);
if (file.exists()) {
deleteFileOrFolderSilently(file);
}
}
public static void deleteFileAtPathSilently(String path) {
deleteFileOrFolderSilently(new File(path));
}
public static void deleteFileOrFolderSilently(File file) {
if (file.isDirectory()) {
File[] files = file.listFiles();
for (File fileEntry : files) {
if (fileEntry.isDirectory()) {
deleteFileOrFolderSilently(fileEntry);
} else {
fileEntry.delete();
}
}
}
if (!file.delete()) {
CodePushUtils.log("Error deleting file " + file.getName());
}
}
public static boolean fileAtPathExists(String filePath) {
return new File(filePath).exists();
}
public static void moveFile(File fileToMove, String newFolderPath, String newFileName) {
File newFolder = new File(newFolderPath);
if (!newFolder.exists()) {
newFolder.mkdirs();
}
File newFilePath = new File(newFolderPath, newFileName);
if (!fileToMove.renameTo(newFilePath)) {
throw new CodePushUnknownException("Unable to move file from " +
fileToMove.getAbsolutePath() + " to " + newFilePath.getAbsolutePath() + ".");
}
}
public static String readFileToString(String filePath) throws IOException {
FileInputStream fin = null;
BufferedReader reader = null;
try {
File fl = new File(filePath);
fin = new FileInputStream(fl);
reader = new BufferedReader(new InputStreamReader(fin));
StringBuilder sb = new StringBuilder();
String line = null;
while ((line = reader.readLine()) != null) {
sb.append(line).append("\n");
}
return sb.toString();
} finally {
if (reader != null) reader.close();
if (fin != null) fin.close();
}
}
private static String validateFileName(String fileName, File destinationFolder) throws IOException {
String destinationFolderCanonicalPath = destinationFolder.getCanonicalPath() + File.separator;
File file = new File(destinationFolderCanonicalPath, fileName);
String canonicalPath = file.getCanonicalPath();
if (!canonicalPath.startsWith(destinationFolderCanonicalPath)) {
throw new IllegalStateException("File is outside extraction target directory.");
}
return canonicalPath;
}
public static void unzipFile(File zipFile, String destination) throws IOException {
FileInputStream fileStream = null;
BufferedInputStream bufferedStream = null;
ZipInputStream zipStream = null;
try {
fileStream = new FileInputStream(zipFile);
bufferedStream = new BufferedInputStream(fileStream);
zipStream = new ZipInputStream(bufferedStream);
ZipEntry entry;
File destinationFolder = new File(destination);
if (destinationFolder.exists()) {
deleteFileOrFolderSilently(destinationFolder);
}
destinationFolder.mkdirs();
byte[] buffer = new byte[WRITE_BUFFER_SIZE];
while ((entry = zipStream.getNextEntry()) != null) {
String fileName = validateFileName(entry.getName(), destinationFolder);
File file = new File(fileName);
if (entry.isDirectory()) {
file.mkdirs();
} else {
File parent = file.getParentFile();
if (!parent.exists()) {
parent.mkdirs();
}
FileOutputStream fout = new FileOutputStream(file);
try {
int numBytesRead;
while ((numBytesRead = zipStream.read(buffer)) != -1) {
fout.write(buffer, 0, numBytesRead);
}
} finally {
fout.close();
}
}
long time = entry.getTime();
if (time > 0) {
file.setLastModified(time);
}
}
} finally {
try {
if (zipStream != null) zipStream.close();
if (bufferedStream != null) bufferedStream.close();
if (fileStream != null) fileStream.close();
} catch (IOException e) {
throw new CodePushUnknownException("Error closing IO resources.", e);
}
}
}
public static void writeStringToFile(String content, String filePath) throws IOException {
PrintWriter out = null;
try {
out = new PrintWriter(filePath);
out.print(content);
} finally {
if (out != null) out.close();
}
}
}
================================================
FILE: android/app/src/main/java/com/microsoft/codepush/react/ReactInstanceHolder.java
================================================
package com.microsoft.codepush.react;
import com.facebook.react.ReactInstanceManager;
/**
* Provides access to a {@link ReactInstanceManager}.
*
* ReactNativeHost already implements this interface, if you make use of that react-native
* component (just add `implements ReactInstanceHolder`).
*/
public interface ReactInstanceHolder {
/**
* Get the current {@link ReactInstanceManager} instance. May return null.
*/
ReactInstanceManager getReactInstanceManager();
}
================================================
FILE: android/app/src/main/java/com/microsoft/codepush/react/SettingsManager.java
================================================
package com.microsoft.codepush.react;
import android.content.Context;
import android.content.SharedPreferences;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
public class SettingsManager {
private SharedPreferences mSettings;
public SettingsManager(Context applicationContext) {
mSettings = applicationContext.getSharedPreferences(CodePushConstants.CODE_PUSH_PREFERENCES, 0);
}
public JSONArray getFailedUpdates() {
String failedUpdatesString = mSettings.getString(CodePushConstants.FAILED_UPDATES_KEY, null);
if (failedUpdatesString == null) {
return new JSONArray();
}
try {
return new JSONArray(failedUpdatesString);
} catch (JSONException e) {
// Unrecognized data format, clear and replace with expected format.
JSONArray emptyArray = new JSONArray();
mSettings.edit().putString(CodePushConstants.FAILED_UPDATES_KEY, emptyArray.toString()).commit();
return emptyArray;
}
}
public JSONObject getPendingUpdate() {
String pendingUpdateString = mSettings.getString(CodePushConstants.PENDING_UPDATE_KEY, null);
if (pendingUpdateString == null) {
return null;
}
try {
return new JSONObject(pendingUpdateString);
} catch (JSONException e) {
// Should not happen.
CodePushUtils.log("Unable to parse pending update metadata " + pendingUpdateString +
" stored in SharedPreferences");
return null;
}
}
public boolean isFailedHash(String packageHash) {
JSONArray failedUpdates = getFailedUpdates();
if (packageHash != null) {
for (int i = 0; i < failedUpdates.length(); i++) {
try {
JSONObject failedPackage = failedUpdates.getJSONObject(i);
String failedPackageHash = failedPackage.getString(CodePushConstants.PACKAGE_HASH_KEY);
if (packageHash.equals(failedPackageHash)) {
return true;
}
} catch (JSONException e) {
throw new CodePushUnknownException("Unable to read failedUpdates data stored in SharedPreferences.", e);
}
}
}
return false;
}
public boolean isPendingUpdate(String packageHash) {
JSONObject pendingUpdate = getPendingUpdate();
try {
return pendingUpdate != null &&
!pendingUpdate.getBoolean(CodePushConstants.PENDING_UPDATE_IS_LOADING_KEY) &&
(packageHash == null || pendingUpdate.getString(CodePushConstants.PENDING_UPDATE_HASH_KEY).equals(packageHash));
} catch (JSONException e) {
throw new CodePushUnknownException("Unable to read pending update metadata in isPendingUpdate.", e);
}
}
public void removeFailedUpdates() {
mSettings.edit().remove(CodePushConstants.FAILED_UPDATES_KEY).commit();
}
public void removePendingUpdate() {
mSettings.edit().remove(CodePushConstants.PENDING_UPDATE_KEY).commit();
}
public void saveFailedUpdate(JSONObject failedPackage) {
try {
if (isFailedHash(failedPackage.getString(CodePushConstants.PACKAGE_HASH_KEY))) {
// Do not need to add the package if it is already in the failedUpdates.
return;
}
} catch (JSONException e) {
throw new CodePushUnknownException("Unable to read package hash from package.", e);
}
String failedUpdatesString = mSettings.getString(CodePushConstants.FAILED_UPDATES_KEY, null);
JSONArray failedUpdates;
if (failedUpdatesString == null) {
failedUpdates = new JSONArray();
} else {
try {
failedUpdates = new JSONArray(failedUpdatesString);
} catch (JSONException e) {
// Should not happen.
throw new CodePushMalformedDataException("Unable to parse failed updates information " +
failedUpdatesString + " stored in SharedPreferences", e);
}
}
failedUpdates.put(failedPackage);
mSettings.edit().putString(CodePushConstants.FAILED_UPDATES_KEY, failedUpdates.toString()).commit();
}
public JSONObject getLatestRollbackInfo() {
String latestRollbackInfoString = mSettings.getString(CodePushConstants.LATEST_ROLLBACK_INFO_KEY, null);
if (latestRollbackInfoString == null) {
return null;
}
try {
return new JSONObject(latestRollbackInfoString);
} catch (JSONException e) {
// Should not happen.
CodePushUtils.log("Unable to parse latest rollback metadata " + latestRollbackInfoString +
" stored in SharedPreferences");
return null;
}
}
public void setLatestRollbackInfo(String packageHash) {
JSONObject latestRollbackInfo = getLatestRollbackInfo();
int count = 0;
if (latestRollbackInfo != null) {
try {
String latestRollbackPackageHash = latestRollbackInfo.getString(CodePushConstants.LATEST_ROLLBACK_PACKAGE_HASH_KEY);
if (latestRollbackPackageHash.equals(packageHash)) {
count = latestRollbackInfo.getInt(CodePushConstants.LATEST_ROLLBACK_COUNT_KEY);
}
} catch (JSONException e) {
CodePushUtils.log("Unable to parse latest rollback info.");
}
} else {
latestRollbackInfo = new JSONObject();
}
try {
latestRollbackInfo.put(CodePushConstants.LATEST_ROLLBACK_PACKAGE_HASH_KEY, packageHash);
latestRollbackInfo.put(CodePushConstants.LATEST_ROLLBACK_TIME_KEY, System.currentTimeMillis());
latestRollbackInfo.put(CodePushConstants.LATEST_ROLLBACK_COUNT_KEY, count + 1);
mSettings.edit().putString(CodePushConstants.LATEST_ROLLBACK_INFO_KEY, latestRollbackInfo.toString()).commit();
} catch (JSONException e) {
throw new CodePushUnknownException("Unable to save latest rollback info.", e);
}
}
public void savePendingUpdate(String packageHash, boolean isLoading) {
JSONObject pendingUpdate = new JSONObject();
try {
pendingUpdate.put(CodePushConstants.PENDING_UPDATE_HASH_KEY, packageHash);
pendingUpdate.put(CodePushConstants.PENDING_UPDATE_IS_LOADING_KEY, isLoading);
mSettings.edit().putString(CodePushConstants.PENDING_UPDATE_KEY, pendingUpdate.toString()).commit();
} catch (JSONException e) {
// Should not happen.
throw new CodePushUnknownException("Unable to save pending update.", e);
}
}
}
================================================
FILE: android/app/src/main/java/com/microsoft/codepush/react/TLSSocketFactory.java
================================================
package com.microsoft.codepush.react;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
public class TLSSocketFactory extends SSLSocketFactory {
private SSLSocketFactory delegate;
public TLSSocketFactory() throws KeyManagementException, NoSuchAlgorithmException {
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, null, null);
delegate = context.getSocketFactory();
}
@Override
public String[] getDefaultCipherSuites() {
return delegate.getDefaultCipherSuites();
}
@Override
public String[] getSupportedCipherSuites() {
return delegate.getSupportedCipherSuites();
}
@Override
public Socket createSocket() throws IOException {
return enableTLSOnSocket(delegate.createSocket());
}
@Override
public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
return enableTLSOnSocket(delegate.createSocket(s, host, port, autoClose));
}
@Override
public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
return enableTLSOnSocket(delegate.createSocket(host, port));
}
@Override
public Socket createSocket(String host, int port, InetAddress localHost, int localPort)
throws IOException, UnknownHostException {
return enableTLSOnSocket(delegate.createSocket(host, port, localHost, localPort));
}
@Override
public Socket createSocket(InetAddress host, int port) throws IOException {
return enableTLSOnSocket(delegate.createSocket(host, port));
}
@Override
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort)
throws IOException {
return enableTLSOnSocket(delegate.createSocket(address, port, localAddress, localPort));
}
private Socket enableTLSOnSocket(Socket socket) {
if (socket != null && (socket instanceof SSLSocket)) {
((SSLSocket) socket).setEnabledProtocols(new String[] { "TLSv1.1", "TLSv1.2" });
}
return socket;
}
}
================================================
FILE: android/build.gradle
================================================
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:1.3.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
android {
namespace "com.microsoft.codepush.react"
}
repositories {
mavenLocal()
mavenCentral()
}
}
================================================
FILE: android/codepush.gradle
================================================
// Adapted from https://raw.githubusercontent.com/facebook/react-native/d16ff3bd8b92fa84a9007bf5ebedd8153e4c089d/react.gradle
import java.nio.file.Paths;
def config = project.extensions.findByName("react") ?: [:]
def bundleAssetName = config.bundleAssetName ? config.bundleAssetName.get() : "index.android.bundle"
// because elvis operator
def elvisFile(thing) {
return thing ? file(thing) : null;
}
void runBefore(String dependentTaskName, Task task) {
Task dependentTask = tasks.findByPath(dependentTaskName);
if (dependentTask != null) {
dependentTask.dependsOn task
}
}
/**
* Finds the path of the installed npm package with the given name using Node's
* module resolution algorithm, which searches "node_modules" directories up to
* the file system root. This handles various cases, including:
*
* - Working in the open-source RN repo:
* Gradle: /path/to/react-native/ReactAndroid
* Node module: /path/to/react-native/node_modules/[package]
*
* - Installing RN as a dependency of an app and searching for hoisted
* dependencies:
* Gradle: /path/to/app/node_modules/react-native/ReactAndroid
* Node module: /path/to/app/node_modules/[package]
*
* - Working in a larger repo (e.g., Facebook) that contains RN:
* Gradle: /path/to/repo/path/to/react-native/ReactAndroid
* Node module: /path/to/repo/node_modules/[package]
*
* The search begins at the given base directory (a File object). The returned
* path is a string.
*/
static def findNodeModulePath(baseDir, packageName) {
def basePath = baseDir.toPath().normalize()
// Node's module resolution algorithm searches up to the root directory,
// after which the base path will be null
while (basePath) {
def candidatePath = Paths.get(basePath.toString(), "node_modules", packageName)
if (candidatePath.toFile().exists()) {
return candidatePath.toString()
}
basePath = basePath.getParent()
}
return null
}
android.buildTypes.each { buildType ->
// to prevent incorrect long value restoration from strings.xml we need to wrap it with double quotes
// https://github.com/microsoft/cordova-plugin-code-push/issues/264
buildType.resValue 'string', "CODE_PUSH_APK_BUILD_TIME", String.format("\"%d\"", System.currentTimeMillis())
}
gradle.projectsEvaluated {
def debuggableVariants = config.debuggableVariants ? config.debuggableVariants.get() : ['debug']
android.applicationVariants.all { variant ->
// No code push for debuggable variants
if (debuggableVariants.contains(variant.name)) {
return;
}
def nodeModulesPath;
if (project.hasProperty('nodeModulesPath')) {
nodeModulesPath = "${project.nodeModulesPath}/react-native-code-push"
} else {
nodeModulesPath = findNodeModulePath(projectDir, "react-native-code-push")
}
def targetName = variant.name.capitalize()
def targetPath = variant.dirName
def jsBundleDir;
def resourcesDir;
def jsBundleFile;
// Additional node commandline arguments
def nodeExecutableAndArgs = config.nodeExecutableAndArgs ? config.nodeExecutableAndArgs.get(): ["node"]
def extraPackagerArgs = config.extraPackagerArgs ? config.extraPackagerArgs.get() : []
// Make this task run right after the bundle task
def generateBundledResourcesHash;
def reactBundleTask = tasks.findByName("createBundle${targetName}JsAndAssets")
if (reactBundleTask) {
jsBundleDir = reactBundleTask.property('jsBundleDir').asFile.get()
resourcesDir = reactBundleTask.property('resourcesDir').asFile.get()
// mitigates Resource and asset merger: Duplicate resources error
project.delete(files("${jsBundleDir}"))
jsBundleDir.mkdirs()
resourcesDir.mkdirs()
jsBundleFile = file("$jsBundleDir/$bundleAssetName")
generateBundledResourcesHash = tasks.create(
name: "generateBundledResourcesHash${targetName}",
type: Exec) {
commandLine (*nodeExecutableAndArgs, "${nodeModulesPath}/scripts/generateBundledResourcesHash.js", resourcesDir, jsBundleFile, jsBundleDir)
enabled !debuggableVariants.contains(variant.name) ?: targetName.toLowerCase().contains("release")
}
runBefore("merge${targetName}Resources", generateBundledResourcesHash)
runBefore("merge${targetName}Assets", generateBundledResourcesHash)
} else {
def jsBundleDirConfigName = "jsBundleDir${targetName}"
jsBundleDir = elvisFile(config."$jsBundleDirConfigName") ? elvisFile(config."$jsBundleDirConfigName").get():
file("$buildDir/intermediates/assets/${targetPath}")
def resourcesDirConfigName = "resourcesDir${targetName}"
resourcesDir = elvisFile(config."${resourcesDirConfigName}") ? elvisFile(config."${resourcesDirConfigName}").get():
file("$buildDir/intermediates/res/merged/${targetPath}")
// In case version of 'Android Plugin for Gradle'' is lower than 1.3.0
// '$buildDir' has slightly different structure - 'merged' folder
// does not exists so '${targetPath}' folder contains directly in 'res' folder.
if (!resourcesDir.exists() && file("$buildDir/intermediates/res/${targetPath}").exists()) {
resourcesDir = file("$buildDir/intermediates/res/${targetPath}")
}
jsBundleFile = file("$jsBundleDir/$bundleAssetName")
def resourcesMapTempFileName = "CodePushResourcesMap-" + java.util.UUID.randomUUID().toString().substring(0,8) + ".json"
generateBundledResourcesHash = tasks.create(
name: "generateBundledResourcesHash${targetName}",
type: Exec) {
commandLine (*nodeExecutableAndArgs, "${nodeModulesPath}/scripts/generateBundledResourcesHash.js", resourcesDir, jsBundleFile, jsBundleDir, resourcesMapTempFileName)
}
// Make this task run right before the bundle task
def recordFilesBeforeBundleCommand = tasks.create(
name: "recordFilesBeforeBundleCommand${targetName}",
type: Exec) {
commandLine (*nodeExecutableAndArgs, "${nodeModulesPath}/scripts/recordFilesBeforeBundleCommand.js", resourcesDir, resourcesMapTempFileName)
}
recordFilesBeforeBundleCommand.dependsOn("merge${targetName}Resources")
recordFilesBeforeBundleCommand.dependsOn("merge${targetName}Assets")
runBefore("bundle${targetName}JsAndAssets", recordFilesBeforeBundleCommand)
// We need to generate and record the resources map, but we use it to generate the bundle hash
generateBundledResourcesHash.dependsOn("recordFilesBeforeBundleCommand${targetName}")
}
generateBundledResourcesHash.dependsOn("createBundle${targetName}JsAndAssets")
runBefore("processArmeabi-v7a${targetName}Resources", generateBundledResourcesHash)
runBefore("processX86${targetName}Resources", generateBundledResourcesHash)
runBefore("processUniversal${targetName}Resources", generateBundledResourcesHash)
runBefore("process${targetName}Resources", generateBundledResourcesHash)
}
}
================================================
FILE: android/gradle/wrapper/gradle-wrapper.properties
================================================
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.4-all.zip
================================================
FILE: android/gradle.properties
================================================
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
# Default value: -Xmx10248m -XX:MaxPermSize=256m
# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
android.useDeprecatedNdk=true
================================================
FILE: android/gradlew
================================================
#!/usr/bin/env bash
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn ( ) {
echo "$*"
}
die ( ) {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
esac
# For Cygwin, ensure paths are in UNIX format before anything is touched.
if $cygwin ; then
[ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
fi
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >&-
APP_HOME="`pwd -P`"
cd "$SAVED" >&-
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
JVM_OPTS=("$@")
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
================================================
FILE: android/gradlew.bat
================================================
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windowz variants
if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
goto execute
:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
================================================
FILE: android/settings.gradle
================================================
include ':app'
================================================
FILE: code-push-plugin-testing-framework/package.json
================================================
{
"name": "code-push-plugin-testing-framework",
"version": "0.0.1",
"description": "Plugin Testing Framework for CodePush Plugins",
"main": "script/index.js",
"scripts": {
"test": "gulp"
},
"repository": {
"type": "git",
"url": "git+https://github.com/microsoft/code-push.git"
},
"author": {
"name": "Microsoft Corporation"
},
"license": "MIT",
"homepage": "https://microsoft.github.io/code-push",
"dependencies": {
"@types/uuid": "^8.3.1",
"base-64": "^1.0.0",
"mocha": "latest",
"mocha-junit-reporter": "latest",
"q": "^1.5.1",
"replace": "latest",
"superagent": "^6.1.0",
"superagent-proxy": "^3.0.0",
"uuid": "^8.3.2"
},
"bugs": {
"url": "https://github.com/microsoft/code-push/issues"
},
"readme": "ERROR: No README data found!",
"_id": "code-push-plugin-testing-framework@0.0.1",
"_shasum": "6ea33a661710628af266d714949fe95f88d71f0d",
"_from": "../code-push/plugin-testing-framework/bin",
"_resolved": "file:../code-push/plugin-testing-framework/bin"
}
================================================
FILE: code-push-plugin-testing-framework/script/index.js
================================================
"use strict";
var Platform = require("./platform");
exports.Platform = Platform;
var PluginTestingFramework = require("./test");
exports.PluginTestingFramework = PluginTestingFramework;
var projectManager_1 = require("./projectManager");
exports.ProjectManager = projectManager_1.ProjectManager;
exports.setupTestRunScenario = projectManager_1.setupTestRunScenario;
exports.setupUpdateScenario = projectManager_1.setupUpdateScenario;
var ServerUtil = require("./serverUtil");
exports.ServerUtil = ServerUtil;
var testBuilder_1 = require("./testBuilder");
exports.TestBuilder = testBuilder_1.TestBuilder;
var TestConfig = require("./testConfig");
exports.TestConfig = TestConfig;
var testUtil_1 = require("./testUtil");
exports.TestUtil = testUtil_1.TestUtil;
================================================
FILE: code-push-plugin-testing-framework/script/platform.js
================================================
"use strict";
var Q = require("q");
var testUtil_1 = require("./testUtil");
//////////////////////////////////////////////////////////////////////////////////////////
// PLATFORMS
/**
* Android implementations of IPlatform.
*/
var Android = (function () {
function Android(emulatorManager) {
this.emulatorManager = emulatorManager;
}
/**
* Gets the platform name. (e.g. "android" for the Android platform).
*/
Android.prototype.getName = function () {
return "android";
};
/**
* The command line flag used to determine whether or not this platform should run.
* Runs when the flag is present, doesn't run otherwise.
*/
Android.prototype.getCommandLineFlagName = function () {
return "--android";
};
/**
* Gets the server url used for testing.
*/
Android.prototype.getServerUrl = function () {
if (!this.serverUrl)
this.serverUrl = process.env.ANDROID_SERVER ? process.env.ANDROID_SERVER : Android.DEFAULT_ANDROID_SERVER_URL;
return this.serverUrl;
};
/**
* Gets an IEmulatorManager that is used to control the emulator during the tests.
*/
Android.prototype.getEmulatorManager = function () {
return this.emulatorManager;
};
/**
* Gets the default deployment key.
*/
Android.prototype.getDefaultDeploymentKey = function () {
return "mock-android-deployment-key";
};
Android.DEFAULT_ANDROID_SERVER_URL = "http://10.0.2.2:3001";
return Android;
}());
exports.Android = Android;
/**
* IOS implementation of IPlatform.
*/
var IOS = (function () {
function IOS(emulatorManager) {
this.emulatorManager = emulatorManager;
}
/**
* Gets the platform name. (e.g. "android" for the Android platform).
*/
IOS.prototype.getName = function () {
return "ios";
};
/**
* The command line flag used to determine whether or not this platform should run.
* Runs when the flag is present, doesn't run otherwise.
*/
IOS.prototype.getCommandLineFlagName = function () {
return "--ios";
};
/**
* Gets the server url used for testing.
*/
IOS.prototype.getServerUrl = function () {
if (!this.serverUrl)
this.serverUrl = process.env.IOS_SERVER ? process.env.IOS_SERVER : IOS.DEFAULT_IOS_SERVER_URL;
return this.serverUrl;
};
/**
* Gets an IEmulatorManager that is used to control the emulator during the tests.
*/
IOS.prototype.getEmulatorManager = function () {
return this.emulatorManager;
};
/**
* Gets the default deployment key.
*/
IOS.prototype.getDefaultDeploymentKey = function () {
return "mock-ios-deployment-key";
};
IOS.DEFAULT_IOS_SERVER_URL = "http://127.0.0.1:3000";
return IOS;
}());
exports.IOS = IOS;
//////////////////////////////////////////////////////////////////////////////////////////
// EMULATOR MANAGERS
// bootEmulatorInternal constants
var emulatorMaxReadyAttempts = 50;
var emulatorReadyCheckDelayMs = 5 * 1000;
/**
* Helper function for EmulatorManager implementations to use to boot an emulator with a given platformName and check, start, and kill methods.
*/
function bootEmulatorInternal(platformName, restartEmulators, targetEmulator, checkEmulator, startEmulator, killEmulator) {
var deferred = Q.defer();
console.log("Setting up " + platformName + " emulator.");
function onEmulatorReady() {
console.log(platformName + " emulator is ready!");
deferred.resolve(undefined);
return deferred.promise;
}
// Called to check if the emulator for the platform is initialized.
function checkEmulatorReady() {
var checkDeferred = Q.defer();
console.log("Checking if " + platformName + " emulator is ready yet...");
// Dummy command that succeeds if emulator is ready and fails otherwise.
checkEmulator(targetEmulator)
.then(function () {
checkDeferred.resolve(undefined);
}, function (error) {
console.info(error);
console.log(platformName + " emulator is not ready yet!");
checkDeferred.reject(error);
});
return checkDeferred.promise;
}
var emulatorReadyAttempts = 0;
// Loops checks to see if the emulator is ready and eventually fails after surpassing emulatorMaxReadyAttempts.
function checkEmulatorReadyLooper() {
var looperDeferred = Q.defer();
emulatorReadyAttempts++;
if (emulatorReadyAttempts > emulatorMaxReadyAttempts) {
console.log(platformName + " emulator is not ready after " + emulatorMaxReadyAttempts + " attempts, abort.");
deferred.reject(platformName + " emulator failed to boot.");
looperDeferred.resolve(undefined);
}
setTimeout(function () {
checkEmulatorReady()
.then(function () {
looperDeferred.resolve(undefined);
onEmulatorReady();
}, function () {
return checkEmulatorReadyLooper().then(function () { looperDeferred.resolve(undefined); }, function () { looperDeferred.reject(undefined); });
});
}, emulatorReadyCheckDelayMs);
return looperDeferred.promise;
}
// Starts and loops the emulator.
function startEmulatorAndLoop() {
console.log("Booting " + platformName + " emulator named " + targetEmulator + ".");
startEmulator(targetEmulator).catch(function (error) { console.log(error); deferred.reject(error); });
return checkEmulatorReadyLooper();
}
var promise;
if (restartEmulators) {
console.log("Killing " + platformName + " emulator.");
promise = killEmulator().catch(function () { return null; }).then(startEmulatorAndLoop);
}
else {
promise = checkEmulatorReady().then(onEmulatorReady, startEmulatorAndLoop);
}
return deferred.promise;
}
var AndroidEmulatorManager = (function () {
function AndroidEmulatorManager() {
}
/**
* Returns the target emulator, which is specified through the command line.
*/
AndroidEmulatorManager.prototype.getTargetEmulator = function () {
let _this = this;
if (this.targetEmulator)
return Q(this.targetEmulator);
else {
const deferred = Q.defer();
const targetAndroidEmulator = process.env.ANDROID_EMU;
if (!targetAndroidEmulator) {
// If no Android simulator is specified, get the most recent Android simulator to run tests on.
testUtil_1.TestUtil.getProcessOutput("emulator -list-avds", { noLogCommand: true, noLogStdOut: true, noLogStdErr: true })
.then((Devices) => {
const listOfDevices = Devices.trim().split("\n");
deferred.resolve(listOfDevices[listOfDevices.length - 1]);
}, (error) => {
deferred.reject(error);
});
}
else {
// Use the simulator specified on the command line.
deferred.resolve(targetAndroidEmulator);
}
return deferred.promise
.then((targetEmulator) => {
_this.targetEmulator = targetEmulator;
console.log("Using Android simulator named " + _this.targetEmulator);
return _this.targetEmulator;
});
}
};
/**
* Boots the target emulator.
*/
AndroidEmulatorManager.prototype.bootEmulator = function (restartEmulators) {
function checkAndroidEmulator(androidEmulatorName) {
// A command that does nothing but only succeeds if the emulator is running.
// List all of the packages on the device.
return testUtil_1.TestUtil.getProcessOutput("adb shell pm list packages", { noLogCommand: true, noLogStdOut: true, noLogStdErr: true }).then(function () { return null; });
}
function startAndroidEmulator(androidEmulatorName) {
const androidEmulatorCommand = `emulator @${androidEmulatorName}`;
let osSpecificCommand = "";
if (process.platform === "darwin") {
osSpecificCommand = `${androidEmulatorCommand} &`;
} else {
osSpecificCommand = `START /B ${androidEmulatorCommand}`;
}
return testUtil_1.TestUtil.getProcessOutput(osSpecificCommand, { noLogStdErr: true, timeout: 5000 });
}
function killAndroidEmulator() {
return testUtil_1.TestUtil.getProcessOutput("adb emu kill").then(function () { return null; });
}
return this.getTargetEmulator()
.then(function (targetEmulator) {
return bootEmulatorInternal("Android", restartEmulators, targetEmulator, checkAndroidEmulator, startAndroidEmulator, killAndroidEmulator);
});
};
/**
* Launches an already installed application by app id.
*/
AndroidEmulatorManager.prototype.launchInstalledApplication = function (appId) {
return testUtil_1.TestUtil.getProcessOutput("adb shell monkey -p " + appId + " -c android.intent.category.LAUNCHER 1").then(function () { return null; });
};
/**
* Ends a running application given its app id.
*/
AndroidEmulatorManager.prototype.endRunningApplication = function (appId) {
return testUtil_1.TestUtil.getProcessOutput("adb shell am force-stop " + appId).then(function () { return Q.delay(10000); });
};
/**
* Restarts an already installed application by app id.
*/
AndroidEmulatorManager.prototype.restartApplication = function (appId) {
var _this = this;
return this.endRunningApplication(appId)
.then(function () {
// Wait for a 1 second before restarting.
return Q.delay(1000);
})
.then(function () {
return _this.launchInstalledApplication(appId);
});
};
/**
* Navigates away from the current app, waits for a delay (defaults to 1 second), then navigates to the specified app.
*/
AndroidEmulatorManager.prototype.resumeApplication = function (appId, delayBeforeResumingMs) {
var _this = this;
if (delayBeforeResumingMs === void 0) { delayBeforeResumingMs = 1000; }
// Open a default Android app (for example, settings).
return this.launchInstalledApplication("com.android.settings")
.then(function () {
console.log("Waiting for " + delayBeforeResumingMs + "ms before resuming the test application.");
return Q.delay(delayBeforeResumingMs);
})
.then(function () {
// Reopen the app.
return _this.launchInstalledApplication(appId);
});
};
/**
* Prepares the emulator for a test.
*/
AndroidEmulatorManager.prototype.prepareEmulatorForTest = function (appId) {
return this.endRunningApplication(appId)
.then(function () {
return commandWithCheckAppExistence("adb shell pm clear", appId);
});
};
/**
* Uninstalls the app from the emulator.
*/
AndroidEmulatorManager.prototype.uninstallApplication = function (appId) {
return commandWithCheckAppExistence("adb uninstall", appId);
};
return AndroidEmulatorManager;
}());
exports.AndroidEmulatorManager = AndroidEmulatorManager;
var IOSEmulatorManager = (function () {
function IOSEmulatorManager() {
}
/**
* Returns the target emulator, which is specified through the command line.
*/
IOSEmulatorManager.prototype.getTargetEmulator = function () {
let _this = this;
if (this.targetEmulator)
return Q(this.targetEmulator);
else {
let deferred = Q.defer();
let targetIOSEmulator = process.env.IOS_EMU;
if (!targetIOSEmulator) {
// If no iOS simulator is specified, get the most recent iOS simulator to run tests on.
testUtil_1.TestUtil.getProcessOutput("xcrun simctl list", { noLogCommand: true, noLogStdOut: true, noLogStdErr: true })
.then((listOfDevicesWithDevicePairs) => {
let listOfDevices = listOfDevicesWithDevicePairs.slice(listOfDevicesWithDevicePairs.indexOf("-- iOS"), listOfDevicesWithDevicePairs.indexOf("-- tvOS"));
let phoneDevice = /iPhone\ \S*\ ?.*?\(([0-9A-Z-]*)\)/g;
let match = phoneDevice.exec(listOfDevices);
deferred.resolve(match[1]);
}, (error) => {
deferred.reject(error);
});
}
else {
// Use the simulator specified on the command line.
deferred.resolve(targetIOSEmulator);
}
return deferred.promise
.then((targetEmulator) => {
_this.targetEmulator = targetEmulator;
console.log("Using iOS simulator named " + _this.targetEmulator);
return _this.targetEmulator;
});
}
};
/**
* Boots the target emulator.
*/
IOSEmulatorManager.prototype.bootEmulator = function (restartEmulators) {
function checkIOSEmulator(iOSEmulatorId) {
// A command that does nothing but only succeeds if the emulator is running.
return testUtil_1.TestUtil.getProcessOutput("xcrun simctl getenv booted SIMULATOR_UDID", { noLogCommand: true, noLogStdOut: true, noLogStdErr: true }).then(function (simUdid) {
return simUdid.trim() == iOSEmulatorId.trim() ? true : Promise.reject(new Error('Waiting for device to boot'));
});
}
function startIOSEmulator(iOSEmulatorId) {
return testUtil_1.TestUtil.getProcessOutput("xcrun simctl boot " + iOSEmulatorId, { noLogStdErr: true })
.catch(function (error) { return undefined; /* Always fails because we do not specify a template, which is not necessary to just start the emulator */ }).then(function () { return null; });
}
function killIOSEmulator() {
return testUtil_1.TestUtil.getProcessOutput("xcrun simctl shutdown all").then(function () { return null; });
}
return this.getTargetEmulator()
.then(function (targetEmulator) {
return bootEmulatorInternal("iOS", restartEmulators, targetEmulator, checkIOSEmulator, startIOSEmulator, killIOSEmulator);
});
};
/**
* Launches an already installed application by app id.
*/
IOSEmulatorManager.prototype.launchInstalledApplication = function (appId) {
return testUtil_1.TestUtil.getProcessOutput("xcrun simctl launch booted " + appId, undefined).then(function () { return null; });
};
/**
* Ends a running application given its app id.
*/
IOSEmulatorManager.prototype.endRunningApplication = function (appId) {
return testUtil_1.TestUtil.getProcessOutput("xcrun simctl terminate booted " + appId, undefined).then(function () { return null; })
};
/**
* Restarts an already installed application by app id.
*/
IOSEmulatorManager.prototype.restartApplication = function (appId) {
var _this = this;
return this.endRunningApplication(appId)
.then(function () {
// Wait for a second before restarting.
return Q.delay(1000);
})
.then(function () { return _this.launchInstalledApplication(appId); });
};
/**
* Navigates away from the current app, waits for a delay (defaults to 1 second), then navigates to the specified app.
*/
IOSEmulatorManager.prototype.resumeApplication = function (appId, delayBeforeResumingMs) {
var _this = this;
if (delayBeforeResumingMs === void 0) { delayBeforeResumingMs = 1000; }
// Open a default iOS app (for example, settings).
return this.launchInstalledApplication("com.apple.Preferences")
.then(function () {
console.log("Waiting for " + delayBeforeResumingMs + "ms before resuming the test application.");
return Q.delay(delayBeforeResumingMs);
})
.then(function () {
// Reopen the app.
return _this.launchInstalledApplication(appId);
});
};
/**
* Prepares the emulator for a test.
*/
IOSEmulatorManager.prototype.prepareEmulatorForTest = function (appId) {
return this.endRunningApplication(appId);
};
/**
* Uninstalls the app from the emulator.
*/
IOSEmulatorManager.prototype.uninstallApplication = function (appId) {
return testUtil_1.TestUtil.getProcessOutput("xcrun simctl uninstall booted " + appId).then(function () { return null; });
};
return IOSEmulatorManager;
}());
exports.IOSEmulatorManager = IOSEmulatorManager;
function commandWithCheckAppExistence(command, appId) {
return testUtil_1.TestUtil.getProcessOutput("adb shell pm list packages", { noLogCommand: true, noLogStdOut: true, noLogStdErr: true })
.then((output) => {
return output.includes(appId);
}).then((isAppExist) => {
if (isAppExist) {
return testUtil_1.TestUtil.getProcessOutput(`${command} ${appId}`).then(function () { return null; });
}
console.log(`Command "${command}" is skipped because the application has not yet been installed`)
return null;
});
}
================================================
FILE: code-push-plugin-testing-framework/script/projectManager.js
================================================
"use strict";
var TestConfig = require("./testConfig");
/**
* In charge of project related operations.
*/
var ProjectManager = (function () {
function ProjectManager() {
}
//// ABSTRACT METHODS
// (not actually abstract because there are some issues with our dts generator that causes it to incorrectly generate abstract classes)
/**
* Returns the name of the plugin being tested, for example Cordova or React-Native.
*
* Overwrite this in your implementation!
*/
ProjectManager.prototype.getPluginName = function () { throw ProjectManager.NOT_IMPLEMENTED_ERROR_MSG; };
/**
* Creates a new test application at the specified path, and configures it
* with the given server URL, android and ios deployment keys.
*
* Overwrite this in your implementation!
*/
ProjectManager.prototype.setupProject = function (projectDirectory, templatePath, appName, appNamespace, version) {
if (version === void 0) { version = ProjectManager.DEFAULT_APP_VERSION; }
throw ProjectManager.NOT_IMPLEMENTED_ERROR_MSG;
};
/**
* Sets up the scenario for a test in an already existing project.
*
* Overwrite this in your implementation!
*/
ProjectManager.prototype.setupScenario = function (projectDirectory, appId, templatePath, jsPath, targetPlatform, version) {
if (version === void 0) { version = ProjectManager.DEFAULT_APP_VERSION; }
throw ProjectManager.NOT_IMPLEMENTED_ERROR_MSG;
};
/**
* Creates a CodePush update package zip for a project.
*
* Overwrite this in your implementation!
*/
ProjectManager.prototype.createUpdateArchive = function (projectDirectory, targetPlatform, isDiff) { throw ProjectManager.NOT_IMPLEMENTED_ERROR_MSG; };
/**
* Prepares a specific platform for tests.
*
* Overwrite this in your implementation!
*/
ProjectManager.prototype.preparePlatform = function (projectDirectory, targetPlatform) { throw ProjectManager.NOT_IMPLEMENTED_ERROR_MSG; };
/**
* Cleans up a specific platform after tests.
*
* Overwrite this in your implementation!
*/
ProjectManager.prototype.cleanupAfterPlatform = function (projectDirectory, targetPlatform) { throw ProjectManager.NOT_IMPLEMENTED_ERROR_MSG; };
/**
* Runs the test app on the given target / platform.
*
* Overwrite this in your implementation!
*/
ProjectManager.prototype.runApplication = function (projectDirectory, targetPlatform) { throw ProjectManager.NOT_IMPLEMENTED_ERROR_MSG; };
ProjectManager.DEFAULT_APP_VERSION = "Store version";
ProjectManager.NOT_IMPLEMENTED_ERROR_MSG = "This method is unimplemented! Please extend ProjectManager and overwrite it!";
return ProjectManager;
}());
exports.ProjectManager = ProjectManager;
//////////////////////////////////////////////////////////////////////////////////////////
// Wrapper functions for simpler code in test cases.
/**
* Wrapper for ProjectManager.setupScenario in the TestRun directory.
*/
function setupTestRunScenario(projectManager, targetPlatform, scenarioJsPath, version) {
return projectManager.setupScenario(TestConfig.testRunDirectory, TestConfig.TestNamespace, TestConfig.templatePath, scenarioJsPath, targetPlatform, version);
}
exports.setupTestRunScenario = setupTestRunScenario;
/**
* Creates an update and zip for the test app using the specified scenario and version.
*/
function setupUpdateScenario(projectManager, targetPlatform, scenarioJsPath, version) {
return projectManager.setupScenario(TestConfig.updatesDirectory, TestConfig.TestNamespace, TestConfig.templatePath, scenarioJsPath, targetPlatform, version)
.then(projectManager.createUpdateArchive.bind(projectManager, TestConfig.updatesDirectory, targetPlatform));
}
exports.setupUpdateScenario = setupUpdateScenario;
================================================
FILE: code-push-plugin-testing-framework/script/serverUtil.js
================================================
"use strict";
// IMPORTS
var assert = require("assert");
var bodyParser = require("body-parser");
var express = require("express");
var Q = require("q");
//////////////////////////////////////////////////////////////////////////////////////////
// Use these functions to set up and shut down the server.
/**
* Sets up the server that the test app uses to send test messages and check for and download updates.
*/
function setupServer(targetPlatform) {
console.log("Setting up server at " + targetPlatform.getServerUrl());
var app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(function (req, res, next) {
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader("Access-Control-Allow-Methods", "*");
res.setHeader("Access-Control-Allow-Headers", "origin, content-type, accept, X-CodePush-SDK-Version");
next();
});
app.get("/v0.1/public/codepush/update_check", function (req, res) {
exports.updateCheckCallback && exports.updateCheckCallback(req);
res.send(exports.updateResponse);
console.log("Update check called from the app.");
console.log("Request: " + JSON.stringify(req.query));
console.log("Response: " + JSON.stringify(exports.updateResponse));
});
app.get("/v0.1/public/codepush/report_status/download", function (req, res) {
console.log("Application downloading the package.");
res.download(exports.updatePackagePath);
});
app.post("/reportTestMessage", function (req, res) {
console.log("Application reported a test message.");
console.log("Body: " + JSON.stringify(req.body));
if (!exports.testMessageResponse) {
console.log("Sending OK");
res.sendStatus(200);
}
else {
console.log("Sending body: " + exports.testMessageResponse);
res.status(200).send(exports.testMessageResponse);
}
exports.testMessageCallback && exports.testMessageCallback(req.body);
});
var serverPortRegEx = /:([0-9]+)/;
exports.server = app.listen(+targetPlatform.getServerUrl().match(serverPortRegEx)[1]);
}
exports.setupServer = setupServer;
/**
* Closes the server.
*/
function cleanupServer() {
if (exports.server) {
exports.server.close();
exports.server = undefined;
}
}
exports.cleanupServer = cleanupServer;
//////////////////////////////////////////////////////////////////////////////////////////
// Classes and methods used for sending mock responses to the app.
/**
* Class used to mock the codePush.checkForUpdate() response from the server.
*/
var CheckForUpdateResponseMock = (function () {
function CheckForUpdateResponseMock() {
}
return CheckForUpdateResponseMock;
}());
exports.CheckForUpdateResponseMock = CheckForUpdateResponseMock;
/**
* The model class of the codePush.checkForUpdate() request to the server.
*/
var UpdateCheckRequestMock = (function () {
function UpdateCheckRequestMock() {
}
return UpdateCheckRequestMock;
}());
exports.UpdateCheckRequestMock = UpdateCheckRequestMock;
/**
* Returns a default empty response to give to the app in a checkForUpdate request
*/
function createDefaultResponse() {
var defaultResponse = new CheckForUpdateResponseMock();
defaultResponse.download_url = "";
defaultResponse.is_disabled = false;
defaultResponse.description = "";
defaultResponse.is_available = false;
defaultResponse.is_mandatory = false;
defaultResponse.target_binary_range = "";
defaultResponse.package_hash = "";
defaultResponse.label = "";
defaultResponse.package_size = 0;
defaultResponse.should_run_binary_version = false;
defaultResponse.update_app_version = false;
return defaultResponse;
}
exports.createDefaultResponse = createDefaultResponse;
/**
* Returns a default update response to give to the app in a checkForUpdate request
*/
function createUpdateResponse(mandatory, targetPlatform, randomHash) {
if (mandatory === void 0) { mandatory = false; }
if (randomHash === void 0) { randomHash = true; }
var updateResponse = new CheckForUpdateResponseMock();
updateResponse.is_available = true;
updateResponse.is_disabled = false;
updateResponse.target_binary_range = "1.0.0";
updateResponse.download_url = "mock.url/v0.1/public/codepush/report_status/download";
updateResponse.is_mandatory = mandatory;
updateResponse.label = "mock-update";
updateResponse.package_hash = "12345-67890";
updateResponse.package_size = 12345;
updateResponse.should_run_binary_version = false;
updateResponse.update_app_version = false;
if (!!targetPlatform)
updateResponse.download_url = targetPlatform.getServerUrl() + "/v0.1/public/codepush/report_status/download";
// We need unique hashes to avoid conflicts.
if (randomHash) {
updateResponse.package_hash = "randomHash-" + Math.floor(Math.random() * 10000);
}
return updateResponse;
}
exports.createUpdateResponse = createUpdateResponse;
/**
* Returns a promise that waits for the next set of test messages sent by the app and resolves if that they are equal to the expected messages or rejects if they are not.
*/
function expectTestMessages(expectedMessages) {
var deferred = Q.defer();
var messageIndex = 0;
var lastRequestBody = null;
exports.testMessageCallback = function (requestBody) {
try {
console.log("Message index: " + messageIndex);
// We should ignore duplicated requests. It is only CI issue.
if (lastRequestBody === null || !areEqual(requestBody, lastRequestBody)) {
if (typeof expectedMessages[messageIndex] === "string") {
assert.equal(requestBody.message, expectedMessages[messageIndex]);
}
else {
assert(areEqual(requestBody, expectedMessages[messageIndex]));
}
lastRequestBody = requestBody;
/* end of message array */
if (++messageIndex === expectedMessages.length) {
deferred.resolve(undefined);
}
}
}
catch (e) {
deferred.reject(e);
}
};
return deferred.promise;
}
exports.expectTestMessages = expectTestMessages;
;
//////////////////////////////////////////////////////////////////////////////////////////
// Test messages used by the test app to send state information to the server.
/**
* Contains all the messages sent from the application to the mock server during tests.
*/
var TestMessage = (function () {
function TestMessage() {
}
TestMessage.CHECK_UP_TO_DATE = "CHECK_UP_TO_DATE";
TestMessage.CHECK_UPDATE_AVAILABLE = "CHECK_UPDATE_AVAILABLE";
TestMessage.CHECK_ERROR = "CHECK_ERROR";
TestMessage.DOWNLOAD_SUCCEEDED = "DOWNLOAD_SUCCEEDED";
TestMessage.DOWNLOAD_ERROR = "DOWNLOAD_ERROR";
TestMessage.UPDATE_INSTALLED = "UPDATE_INSTALLED";
TestMessage.INSTALL_ERROR = "INSTALL_ERROR";
TestMessage.DEVICE_READY_AFTER_UPDATE = "DEVICE_READY_AFTER_UPDATE";
TestMessage.UPDATE_FAILED_PREVIOUSLY = "UPDATE_FAILED_PREVIOUSLY";
TestMessage.NOTIFY_APP_READY_SUCCESS = "NOTIFY_APP_READY_SUCCESS";
TestMessage.NOTIFY_APP_READY_FAILURE = "NOTIFY_APP_READY_FAILURE";
TestMessage.SKIPPED_NOTIFY_APPLICATION_READY = "SKIPPED_NOTIFY_APPLICATION_READY";
TestMessage.SYNC_STATUS = "SYNC_STATUS";
TestMessage.RESTART_SUCCEEDED = "RESTART_SUCCEEDED";
TestMessage.RESTART_FAILED = "RESTART_FAILED";
TestMessage.PENDING_PACKAGE = "PENDING_PACKAGE";
TestMessage.CURRENT_PACKAGE = "CURRENT_PACKAGE";
TestMessage.SYNC_UP_TO_DATE = 0;
TestMessage.SYNC_UPDATE_INSTALLED = 1;
TestMessage.SYNC_UPDATE_IGNORED = 2;
TestMessage.SYNC_ERROR = 3;
TestMessage.SYNC_IN_PROGRESS = 4;
TestMessage.SYNC_CHECKING_FOR_UPDATE = 5;
TestMessage.SYNC_AWAITING_USER_ACTION = 6;
TestMessage.SYNC_DOWNLOADING_PACKAGE = 7;
TestMessage.SYNC_INSTALLING_UPDATE = 8;
return TestMessage;
}());
exports.TestMessage = TestMessage;
/**
* Contains all the messages sent from the mock server back to the application during tests.
*/
var TestMessageResponse = (function () {
function TestMessageResponse() {
}
TestMessageResponse.SKIP_NOTIFY_APPLICATION_READY = "SKIP_NOTIFY_APPLICATION_READY";
return TestMessageResponse;
}());
exports.TestMessageResponse = TestMessageResponse;
/**
* Defines the messages sent from the application to the mock server during tests.
*/
var AppMessage = (function () {
function AppMessage(message, args) {
this.message = message;
this.args = args;
}
AppMessage.fromString = function (message) {
return new AppMessage(message, undefined);
};
return AppMessage;
}());
exports.AppMessage = AppMessage;
/**
* Checks if two messages are equal.
*/
function areEqual(m1, m2) {
/* compare objects */
if (m1 === m2) {
return true;
}
/* compare messages */
if (!m1 || !m2 || m1.message !== m2.message) {
return false;
}
/* compare arguments */
if (m1.args === m2.args) {
return true;
}
if (!m1.args || !m2.args || m1.args.length !== m2.args.length) {
return false;
}
for (var i = 0; i < m1.args.length; i++) {
if (m1.args[i] !== m2.args[i]) {
return false;
}
}
return true;
}
exports.areEqual = areEqual;
================================================
FILE: code-push-plugin-testing-framework/script/test.js
================================================
"use strict";
var Q = require("q");
var ServerUtil = require("./serverUtil");
var testBuilder_1 = require("./testBuilder");
var TestConfig = require("./testConfig");
var testUtil_1 = require("./testUtil");
//////////////////////////////////////////////////////////////////////////////////////////
/**
* Call this function to initialize the automated tests.
*/
function initializeTests(projectManager, supportedTargetPlatforms, describeTests) {
// DETERMINE PLATFORMS TO TEST //
/** The platforms to test on. */
var targetPlatforms = [];
supportedTargetPlatforms.forEach(function (supportedPlatform) {
if (testUtil_1.TestUtil.readMochaCommandLineFlag(supportedPlatform.getCommandLineFlagName()))
targetPlatforms.push(supportedPlatform);
});
// Log current configuration
console.log("Initializing tests for " + testUtil_1.TestUtil.getPluginName());
console.log(TestConfig.TestAppName + "\n" + TestConfig.TestNamespace);
console.log("Testing " + TestConfig.thisPluginPath + ".");
targetPlatforms.forEach(function (platform) {
console.log("On " + platform.getName());
});
console.log("test run directory = " + TestConfig.testRunDirectory);
console.log("updates directory = " + TestConfig.updatesDirectory);
if (TestConfig.onlyRunCoreTests)
console.log("--only running core tests--");
if (TestConfig.shouldSetup)
console.log("--setting up--");
if (TestConfig.restartEmulators)
console.log("--restarting emulators--");
// FUNCTIONS //
function cleanupTest() {
console.log("Cleaning up!");
ServerUtil.updateResponse = undefined;
ServerUtil.testMessageCallback = undefined;
ServerUtil.updateCheckCallback = undefined;
ServerUtil.testMessageResponse = undefined;
}
/**
* Sets up tests for each platform.
* Creates the test project directory and the test update directory.
* Starts required emulators.
*/
function setupTests() {
it("sets up tests correctly", function (done) {
var promises = [];
targetPlatforms.forEach(function (platform) {
promises.push(platform.getEmulatorManager().bootEmulator(TestConfig.restartEmulators));
});
console.log("Building test project.");
// create the test project
promises.push(createTestProject(TestConfig.testRunDirectory)
.then(function () {
console.log("Building update project.");
// create the update project
return createTestProject(TestConfig.updatesDirectory);
}).then(function () { return null; }));
Q.all(promises).then(function () { done(); }, function (error) { done(error); });
});
}
/**
* Creates a test project directory at the given path.
*/
function createTestProject(directory) {
return projectManager.setupProject(directory, TestConfig.templatePath, TestConfig.TestAppName, TestConfig.TestNamespace);
}
/**
* Creates and runs the tests from the projectManager and TestBuilderDescribe objects passed to initializeTests.
*/
function createAndRunTests(targetPlatform) {
describe("CodePush", function () {
before(function () {
ServerUtil.setupServer(targetPlatform);
return targetPlatform.getEmulatorManager().uninstallApplication(TestConfig.TestNamespace)
.then(projectManager.preparePlatform.bind(projectManager, TestConfig.testRunDirectory, targetPlatform))
.then(projectManager.preparePlatform.bind(projectManager, TestConfig.updatesDirectory, targetPlatform));
});
after(function () {
ServerUtil.cleanupServer();
return projectManager.cleanupAfterPlatform(TestConfig.testRunDirectory, targetPlatform).then(projectManager.cleanupAfterPlatform.bind(projectManager, TestConfig.updatesDirectory, targetPlatform));
});
testBuilder_1.TestContext.projectManager = projectManager;
testBuilder_1.TestContext.targetPlatform = targetPlatform;
// Build the tests.
describeTests(projectManager, targetPlatform);
});
}
// BEGIN TESTING //
describe("CodePush " + projectManager.getPluginName() + " Plugin", function () {
this.timeout(100 * 60 * 1000);
if (TestConfig.shouldSetup)
describe("Setting Up For Tests", function () { return setupTests(); });
else {
targetPlatforms.forEach(function (platform) {
var prefix = (TestConfig.onlyRunCoreTests ? "Core Tests " : "Tests ") + TestConfig.thisPluginPath + " on ";
describe(prefix + platform.getName(), function () { return createAndRunTests(platform); });
});
}
});
}
exports.initializeTests = initializeTests;
================================================
FILE: code-push-plugin-testing-framework/script/testBuilder.js
================================================
"use strict";
var ServerUtil = require("./serverUtil");
var TestConfig = require("./testConfig");
//////////////////////////////////////////////////////////////////////////////////////////
// Use this class to create and structure the tests.
// Usage is almost identical to Mocha, but with the addition of the optional "scenarioPath" in describe() and the required "isCoreTest" in it().
var TestBuilder = (function () {
function TestBuilder() {
}
TestBuilder.describe = getDescribe();
TestBuilder.it = getIt();
return TestBuilder;
}());
exports.TestBuilder = TestBuilder;
//////////////////////////////////////////////////////////////////////////////////////////
// Mocha mimicry
/** Singleton class for TestBuilder.describe to use internally to define the context. */
var TestContext = (function () {
function TestContext() {
}
return TestContext;
}());
exports.TestContext = TestContext;
function describeInternal(func, description, spec, scenarioPath) {
if (!TestContext.projectManager || !TestContext.targetPlatform) {
throw new Error("TestContext.projectManager or TestContext.targetPlatform are not defined! Did you call TestBuilder.describe outside of a function you passed to PluginTestingFramework.initializeTests?");
}
return func(description, function () {
afterEach(function () {
console.log("Cleaning up!");
ServerUtil.updateResponse = undefined;
ServerUtil.testMessageCallback = undefined;
ServerUtil.updateCheckCallback = undefined;
ServerUtil.testMessageResponse = undefined;
});
beforeEach(function () {
return TestContext.targetPlatform.getEmulatorManager().prepareEmulatorForTest(TestConfig.TestNamespace)
.catch(function () { });
});
if (scenarioPath) {
before(function () {
return TestContext.projectManager.setupScenario(TestConfig.testRunDirectory, TestConfig.TestNamespace, TestConfig.templatePath, scenarioPath, TestContext.targetPlatform);
});
}
spec();
});
}
/**
* Returns a hybrid type that mimics mocha's describe object.
*/
function getDescribe() {
var describer = function (description, spec, scenarioPath) {
describeInternal(describe, description, spec, scenarioPath);
};
describer.only = function (description, spec, scenarioPath) {
describeInternal(describe.only, description, spec, scenarioPath);
};
describer.skip = function (description, spec, scenarioPath) {
describeInternal(describe.skip, description, spec, scenarioPath);
};
return describer;
}
function itInternal(func, expectation, isCoreTest, assertion) {
if ((!TestConfig.onlyRunCoreTests || isCoreTest)) {
// Create a wrapper around the assertion to set the timeout on the test to 10 minutes.
var assertionWithTimeout = function (done) {
this.timeout(10 * 2 * 60 * 1000);
assertion(done);
};
return it(expectation, assertionWithTimeout);
}
return null;
}
/**
* Returns a hybrid type that mimics mocha's it object.
*/
function getIt() {
var itr = function (expectation, isCoreTest, assertion) {
itInternal(it, expectation, isCoreTest, assertion);
};
itr.only = function (expectation, isCoreTest, assertion) {
itInternal(it.only, expectation, isCoreTest, assertion);
};
itr.skip = function (expectation, isCoreTest, assertion) {
itInternal(it.skip, expectation, isCoreTest, assertion);
};
return itr;
}
================================================
FILE: code-push-plugin-testing-framework/script/testConfig.js
================================================
"use strict";
// IMPORTS //
var os = require("os");
var path = require("path");
var TestUtil_1 = require("./testUtil");
//////////////////////////////////////////////////////////////////////////////////////////
// Configuration variables.
// What plugin to use, what project directories to use, etc.
// COMMAND LINE OPTION NAMES, FLAGS, AND DEFAULTS
var DEFAULT_TEST_RUN_DIRECTORY = path.join(os.tmpdir(), TestUtil_1.TestUtil.getPluginName(), "test-run");
var DEFAULT_UPDATES_DIRECTORY = path.join(os.tmpdir(), TestUtil_1.TestUtil.getPluginName(), "updates");
var DEFAULT_PLUGIN_PATH = path.join(__dirname, "../..");
var NPM_PLUGIN_PATH = TestUtil_1.TestUtil.getPluginName();
var SETUP_FLAG_NAME = "--setup";
var DEFAULT_PLUGIN_TGZ_NAME = TestUtil_1.TestUtil.getPluginName() + "-" + TestUtil_1.TestUtil.getPluginVersion() + ".tgz";
// CONST VARIABLES
exports.TestAppName = "TestCodePush";
exports.TestNamespace = "com.testcodepush";
exports.AcquisitionSDKPluginName = "code-push";
exports.templatePath = path.join(__dirname, "../../test/template");
exports.thisPluginInstallString = TestUtil_1.TestUtil.resolveBooleanVariables(process.env.NPM) ? `npm install ${NPM_PLUGIN_PATH}` : `npm pack ${DEFAULT_PLUGIN_PATH} && npm install ${DEFAULT_PLUGIN_TGZ_NAME} && npm link`;
exports.testRunDirectory = process.env.RUN_DIR ? process.env.RUN_DIR: DEFAULT_TEST_RUN_DIRECTORY;
exports.updatesDirectory = process.env.UPDATE_DIR ? process.env.UPDATE_DIR : DEFAULT_UPDATES_DIRECTORY;
exports.onlyRunCoreTests = TestUtil_1.TestUtil.resolveBooleanVariables(process.env.CORE);
exports.shouldSetup = TestUtil_1.TestUtil.readMochaCommandLineFlag(SETUP_FLAG_NAME);
exports.restartEmulators = TestUtil_1.TestUtil.resolveBooleanVariables(process.env.CLEAN);
================================================
FILE: code-push-plugin-testing-framework/script/testUtil.js
================================================
"use strict";
var archiver = require("archiver");
var child_process = require("child_process");
var fs = require("fs");
var replace = require("replace");
var Q = require("q");
var TestUtil = (function () {
function TestUtil() {
}
//// Command Line Input Functions
/**
* Reads a command line option passed to mocha and returns a default if unspecified.
*/
TestUtil.readMochaCommandLineOption = function (optionName, defaultValue) {
var optionValue = undefined;
for (var i = 0; i < process.argv.length; i++) {
if (process.argv[i] === optionName) {
if (i + 1 < process.argv.length) {
optionValue = process.argv[i + 1];
}
break;
}
}
if (!optionValue)
optionValue = defaultValue;
return optionValue;
};
/**
* Reads command line options passed to mocha.
*/
TestUtil.readMochaCommandLineFlag = function (optionName) {
for (var i = 0; i < process.argv.length; i++) {
if (process.argv[i] === optionName) {
return true;
}
}
return false;
};
//// Utility Functions
/**
* Executes a child process and returns a promise that resolves with its output or rejects with its error.
*/
TestUtil.getProcessOutput = function (command, options) {
var deferred = Q.defer();
options = options || {};
// set default options
if (options.maxBuffer === undefined)
options.maxBuffer = 1024 * 1024 * 500;
if (options.timeout === undefined)
options.timeout = 10 * 60 * 1000;
if (!options.noLogCommand)
console.log("Running command: " + command);
var execProcess = child_process.exec(command, options, function (error, stdout, stderr) {
if (error) {
if (!options.noLogStdErr)
console.error("" + error);
deferred.reject(error);
}
else {
deferred.resolve(stdout.toString());
}
});
if (!options.noLogStdOut)
execProcess.stdout.pipe(process.stdout);
if (!options.noLogStdErr)
execProcess.stderr.pipe(process.stderr);
execProcess.on('error', function (error) {
if (!options.noLogStdErr)
console.error("" + error);
deferred.reject(error);
});
return deferred.promise;
};
/**
* Returns the name of the plugin that is being tested.
*/
TestUtil.getPluginName = function () {
var packageFile = JSON.parse(fs.readFileSync("./package.json", "utf8"));
return packageFile.name;
};
TestUtil.getPluginVersion = function () {
var packageFile = JSON.parse(fs.readFileSync("./package.json", "utf8"));
return packageFile.version;
};
/**
* Replaces a regex in a file with a given string.
*/
TestUtil.replaceString = function (filePath, regex, replacement) {
console.log("replacing \"" + regex + "\" with \"" + replacement + "\" in " + filePath);
replace({ regex: regex, replacement: replacement, recursive: false, silent: true, paths: [filePath] });
};
/**
* Copies a file from a given location to another.
*/
TestUtil.copyFile = function (source, destination, overwrite) {
var deferred = Q.defer();
try {
var errorHandler = function (error) {
deferred.reject(error);
};
if (overwrite && fs.existsSync(destination)) {
fs.unlinkSync(destination);
}
var readStream = fs.createReadStream(source);
readStream.on("error", errorHandler);
var writeStream = fs.createWriteStream(destination);
writeStream.on("error", errorHandler);
writeStream.on("close", deferred.resolve.bind(undefined, undefined));
readStream.pipe(writeStream);
}
catch (e) {
deferred.reject(e);
}
return deferred.promise;
};
/**
* Archives the contents of sourceFolder and puts it in an archive at archivePath in targetFolder.
*/
TestUtil.archiveFolder = function (sourceFolder, targetFolder, archivePath, isDiff) {
var deferred = Q.defer();
var archive = archiver.create("zip", {});
console.log("Creating an update archive at: " + archivePath);
if (fs.existsSync(archivePath)) {
fs.unlinkSync(archivePath);
}
var writeStream = fs.createWriteStream(archivePath);
writeStream.on("close", function () {
deferred.resolve(archivePath);
});
archive.on("error", function (e) {
deferred.reject(e);
});
if (isDiff) {
archive.append("{\"deletedFiles\":[]}", { name: "hotcodepush.json" });
}
archive.directory(sourceFolder, targetFolder);
archive.pipe(writeStream);
archive.finalize();
return deferred.promise;
};
/**
* Check that boolean environment variable string is 'true.
*/
TestUtil.resolveBooleanVariables = function(variable) {
if (variable) {
return variable.toLowerCase() === 'true';
}
return false;
}
//// Placeholders
// Used in the template to represent data that needs to be added by the testing framework at runtime.
TestUtil.ANDROID_KEY_PLACEHOLDER = "CODE_PUSH_ANDROID_DEPLOYMENT_KEY";
TestUtil.IOS_KEY_PLACEHOLDER = "CODE_PUSH_IOS_DEPLOYMENT_KEY";
TestUtil.SERVER_URL_PLACEHOLDER = "CODE_PUSH_SERVER_URL";
TestUtil.INDEX_JS_PLACEHOLDER = "CODE_PUSH_INDEX_JS_PATH";
TestUtil.CODE_PUSH_APP_VERSION_PLACEHOLDER = "CODE_PUSH_APP_VERSION";
TestUtil.CODE_PUSH_TEST_APP_NAME_PLACEHOLDER = "CODE_PUSH_TEST_APP_NAME";
TestUtil.CODE_PUSH_APP_ID_PLACEHOLDER = "CODE_PUSH_TEST_APPLICATION_ID";
TestUtil.PLUGIN_VERSION_PLACEHOLDER = "CODE_PUSH_PLUGIN_VERSION";
return TestUtil;
}());
exports.TestUtil = TestUtil;
================================================
FILE: code-push-plugin-testing-framework/typings/code-push-plugin-testing-framework.d.ts
================================================
declare module 'code-push-plugin-testing-framework/script/platform' {
import Q = require("q");
/**
* Defines a platform supported by CodePush.
*/
export interface IPlatform {
/**
* Gets the platform name. (e.g. "android" for the Android platform).
*/
getName(): string;
/**
* The command line flag used to determine whether or not this platform should run.
* Runs when the flag is present, doesn't run otherwise.
*/
getCommandLineFlagName(): string;
/**
* Gets the server url used for testing.
*/
getServerUrl(): string;
/**
* Gets an IEmulatorManager that is used to control the emulator during the tests.
*/
getEmulatorManager(): IEmulatorManager;
/**
* Gets the default deployment key.
*/
getDefaultDeploymentKey(): string;
}
/**
* Manages the interaction with the emulator.
*/
export interface IEmulatorManager {
/**
* Returns the target emulator, which is specified through the command line.
*/
getTargetEmulator(): Q.Promise;
/**
* Boots the target emulator.
*/
bootEmulator(restartEmulators: boolean): Q.Promise;
/**
* Launches an already installed application by app id.
*/
launchInstalledApplication(appId: string): Q.Promise;
/**
* Ends a running application given its app id.
*/
endRunningApplication(appId: string): Q.Promise;
/**
* Restarts an already installed application by app id.
*/
restartApplication(appId: string): Q.Promise;
/**
* Navigates away from the current app, waits for a delay (defaults to 1 second), then navigates to the specified app.
*/
resumeApplication(appId: string, delayBeforeResumingMs?: number): Q.Promise;
/**
* Prepares the emulator for a test.
*/
prepareEmulatorForTest(appId: string): Q.Promise;
/**
* Uninstalls the app from the emulator.
*/
uninstallApplication(appId: string): Q.Promise;
}
/**
* Android implementations of IPlatform.
*/
export class Android implements IPlatform {
private emulatorManager;
private serverUrl;
constructor(emulatorManager: IEmulatorManager);
/**
* Gets the platform name. (e.g. "android" for the Android platform).
*/
getName(): string;
/**
* The command line flag used to determine whether or not this platform should run.
* Runs when the flag is present, doesn't run otherwise.
*/
getCommandLineFlagName(): string;
private static DEFAULT_ANDROID_SERVER_URL;
/**
* Gets the server url used for testing.
*/
getServerUrl(): string;
/**
* Gets an IEmulatorManager that is used to control the emulator during the tests.
*/
getEmulatorManager(): IEmulatorManager;
/**
* Gets the default deployment key.
*/
getDefaultDeploymentKey(): string;
}
/**
* IOS implementation of IPlatform.
*/
export class IOS implements IPlatform {
private emulatorManager;
private serverUrl;
constructor(emulatorManager: IEmulatorManager);
/**
* Gets the platform name. (e.g. "android" for the Android platform).
*/
getName(): string;
/**
* The command line flag used to determine whether or not this platform should run.
* Runs when the flag is present, doesn't run otherwise.
*/
getCommandLineFlagName(): string;
private static DEFAULT_IOS_SERVER_URL;
/**
* Gets the server url used for testing.
*/
getServerUrl(): string;
/**
* Gets an IEmulatorManager that is used to control the emulator during the tests.
*/
getEmulatorManager(): IEmulatorManager;
/**
* Gets the default deployment key.
*/
getDefaultDeploymentKey(): string;
}
export class AndroidEmulatorManager implements IEmulatorManager {
private targetEmulator;
/**
* Returns the target emulator, which is specified through the command line.
*/
getTargetEmulator(): Q.Promise;
/**
* Boots the target emulator.
*/
bootEmulator(restartEmulators: boolean): Q.Promise;
/**
* Launches an already installed application by app id.
*/
launchInstalledApplication(appId: string): Q.Promise;
/**
* Ends a running application given its app id.
*/
endRunningApplication(appId: string): Q.Promise;
/**
* Restarts an already installed application by app id.
*/
restartApplication(appId: string): Q.Promise;
/**
* Navigates away from the current app, waits for a delay (defaults to 1 second), then navigates to the specified app.
*/
resumeApplication(appId: string, delayBeforeResumingMs?: number): Q.Promise;
/**
* Prepares the emulator for a test.
*/
prepareEmulatorForTest(appId: string): Q.Promise;
/**
* Uninstalls the app from the emulator.
*/
uninstallApplication(appId: string): Q.Promise;
}
export class IOSEmulatorManager implements IEmulatorManager {
private targetEmulator;
/**
* Returns the target emulator, which is specified through the command line.
*/
getTargetEmulator(): Q.Promise;
/**
* Boots the target emulator.
*/
bootEmulator(restartEmulators: boolean): Q.Promise;
/**
* Launches an already installed application by app id.
*/
launchInstalledApplication(appId: string): Q.Promise;
/**
* Ends a running application given its app id.
*/
endRunningApplication(appId: string): Q.Promise;
/**
* Restarts an already installed application by app id.
*/
restartApplication(appId: string): Q.Promise;
/**
* Navigates away from the current app, waits for a delay (defaults to 1 second), then navigates to the specified app.
*/
resumeApplication(appId: string, delayBeforeResumingMs?: number): Q.Promise;
/**
* Prepares the emulator for a test.
*/
prepareEmulatorForTest(appId: string): Q.Promise;
/**
* Uninstalls the app from the emulator.
*/
uninstallApplication(appId: string): Q.Promise;
}
}
declare module 'code-push-plugin-testing-framework/script/projectManager' {
import Q = require("q");
import platform = require('code-push-plugin-testing-framework/script/platform');
/**
* In charge of project related operations.
*/
export class ProjectManager {
static DEFAULT_APP_VERSION: string;
private static NOT_IMPLEMENTED_ERROR_MSG;
/**
* Returns the name of the plugin being tested, for example Cordova or React-Native.
*
* Overwrite this in your implementation!
*/
getPluginName(): string;
/**
* Creates a new test application at the specified path, and configures it
* with the given server URL, android and ios deployment keys.
*
* Overwrite this in your implementation!
*/
setupProject(projectDirectory: string, templatePath: string, appName: string, appNamespace: string, version?: string): Q.Promise;
/**
* Sets up the scenario for a test in an already existing project.
*
* Overwrite this in your implementation!
*/
setupScenario(projectDirectory: string, appId: string, templatePath: string, jsPath: string, targetPlatform: platform.IPlatform, version?: string): Q.Promise;
/**
* Creates a CodePush update package zip for a project.
*
* Overwrite this in your implementation!
*/
createUpdateArchive(projectDirectory: string, targetPlatform: platform.IPlatform, isDiff?: boolean): Q.Promise;
/**
* Prepares a specific platform for tests.
*
* Overwrite this in your implementation!
*/
preparePlatform(projectDirectory: string, targetPlatform: platform.IPlatform): Q.Promise;
/**
* Cleans up a specific platform after tests.
*
* Overwrite this in your implementation!
*/
cleanupAfterPlatform(projectDirectory: string, targetPlatform: platform.IPlatform): Q.Promise;
/**
* Runs the test app on the given target / platform.
*
* Overwrite this in your implementation!
*/
runApplication(projectDirectory: string, targetPlatform: platform.IPlatform): Q.Promise;
}
/**
* Wrapper for ProjectManager.setupScenario in the TestRun directory.
*/
export function setupTestRunScenario(projectManager: ProjectManager, targetPlatform: platform.IPlatform, scenarioJsPath: string, version?: string): Q.Promise;
/**
* Creates an update and zip for the test app using the specified scenario and version.
*/
export function setupUpdateScenario(projectManager: ProjectManager, targetPlatform: platform.IPlatform, scenarioJsPath: string, version: string): Q.Promise;
}
declare module 'code-push-plugin-testing-framework/script/test' {
import Platform = require('code-push-plugin-testing-framework/script/platform');
import { ProjectManager } from 'code-push-plugin-testing-framework/script/projectManager';
/**
* Call this function to initialize the automated tests.
*/
export function initializeTests(projectManager: ProjectManager, supportedTargetPlatforms: Platform.IPlatform[], describeTests: (projectManager: ProjectManager, targetPlatform: Platform.IPlatform) => void): void;
}
declare module 'code-push-plugin-testing-framework/script/serverUtil' {
import platform = require('code-push-plugin-testing-framework/script/platform');
import Q = require("q");
/** The server to respond to requests from the app. */
export var server: any;
/** Response the server gives the next update check request */
export var updateResponse: any;
/** Response the server gives the next test message request */
export var testMessageResponse: any;
/** Called after the next test message request */
export var testMessageCallback: (requestBody: any) => void;
/** Called after the next update check request */
export var updateCheckCallback: (requestBody: any) => void;
/** Location of the update package given in the update check response */
export var updatePackagePath: string;
/**
* Sets up the server that the test app uses to send test messages and check for and download updates.
*/
export function setupServer(targetPlatform: platform.IPlatform): void;
/**
* Closes the server.
*/
export function cleanupServer(): void;
/**
* Class used to mock the codePush.checkForUpdate() response from the server.
*/
export class CheckForUpdateResponseMock {
download_url: string;
is_available: boolean;
should_run_binary_version: boolean;
package_size: number;
update_app_version: boolean;
target_binary_range: string;
is_disabled: boolean;
description: string;
label: string;
package_hash: string;
is_mandatory: boolean;
}
/**
* The model class of the codePush.checkForUpdate() request to the server.
*/
export class UpdateCheckRequestMock {
deploymentKey: string;
appVersion: string;
packageHash: string;
isCompanion: boolean;
}
/**
* Returns a default empty response to give to the app in a checkForUpdate request
*/
export function createDefaultResponse(): CheckForUpdateResponseMock;
/**
* Returns a default update response to give to the app in a checkForUpdate request
*/
export function createUpdateResponse(mandatory?: boolean, targetPlatform?: platform.IPlatform, randomHash?: boolean): CheckForUpdateResponseMock;
/**
* Returns a promise that waits for the next set of test messages sent by the app and resolves if that they are equal to the expected messages or rejects if they are not.
*/
export function expectTestMessages(expectedMessages: (string | AppMessage)[]): Q.Promise;
/**
* Contains all the messages sent from the application to the mock server during tests.
*/
export class TestMessage {
static CHECK_UP_TO_DATE: string;
static CHECK_UPDATE_AVAILABLE: string;
static CHECK_ERROR: string;
static DOWNLOAD_SUCCEEDED: string;
static DOWNLOAD_ERROR: string;
static UPDATE_INSTALLED: string;
static INSTALL_ERROR: string;
static DEVICE_READY_AFTER_UPDATE: string;
static UPDATE_FAILED_PREVIOUSLY: string;
static NOTIFY_APP_READY_SUCCESS: string;
static NOTIFY_APP_READY_FAILURE: string;
static SKIPPED_NOTIFY_APPLICATION_READY: string;
static SYNC_STATUS: string;
static RESTART_SUCCEEDED: string;
static RESTART_FAILED: string;
static PENDING_PACKAGE: string;
static CURRENT_PACKAGE: string;
static SYNC_UP_TO_DATE: number;
static SYNC_UPDATE_INSTALLED: number;
static SYNC_UPDATE_IGNORED: number;
static SYNC_ERROR: number;
static SYNC_IN_PROGRESS: number;
static SYNC_CHECKING_FOR_UPDATE: number;
static SYNC_AWAITING_USER_ACTION: number;
static SYNC_DOWNLOADING_PACKAGE: number;
static SYNC_INSTALLING_UPDATE: number;
}
/**
* Contains all the messages sent from the mock server back to the application during tests.
*/
export class TestMessageResponse {
static SKIP_NOTIFY_APPLICATION_READY: string;
}
/**
* Defines the messages sent from the application to the mock server during tests.
*/
export class AppMessage {
message: string;
args: any[];
constructor(message: string, args: any[]);
static fromString(message: string): AppMessage;
}
/**
* Checks if two messages are equal.
*/
export function areEqual(m1: AppMessage, m2: AppMessage): boolean;
}
declare module 'code-push-plugin-testing-framework/script/testBuilder' {
import Platform = require('code-push-plugin-testing-framework/script/platform');
import { ProjectManager } from 'code-push-plugin-testing-framework/script/projectManager';
export class TestBuilder {
static describe: ITestBuilderContextDefintion;
static it: ITestBuilderTestDefinition;
}
/** Singleton class for TestBuilder.describe to use internally to define the context. */
export class TestContext {
static projectManager: ProjectManager;
static targetPlatform: Platform.IPlatform;
}
export interface ITestBuilderContextDefintion {
(description: string, spec: () => void, scenarioPath?: string): void;
only(description: string, spec: () => void, scenarioPath?: string): void;
skip(description: string, spec: () => void, scenarioPath?: string): void;
}
export interface ITestBuilderTestDefinition {
(expectation: string, isCoreTest: boolean, assertion: (done: Mocha.Done) => void): void;
only(expectation: string, isCoreTest: boolean, assertion: (done: Mocha.Done) => void): void;
skip(expectation: string, isCoreTest: boolean, assertion: (done: Mocha.Done) => void): void;
}
}
declare module 'code-push-plugin-testing-framework/script/testConfig' {
export const TestAppName: string;
export const TestNamespace: string;
export const AcquisitionSDKPluginName: string;
export const templatePath: string;
export const thisPluginInstallString: string;
export const testRunDirectory: string;
export const updatesDirectory: string;
export const onlyRunCoreTests: boolean;
export const shouldSetup: boolean;
export const restartEmulators: boolean;
}
declare module 'code-push-plugin-testing-framework/script/testUtil' {
import Q = require("q");
export class TestUtil {
static ANDROID_KEY_PLACEHOLDER: string;
static IOS_KEY_PLACEHOLDER: string;
static SERVER_URL_PLACEHOLDER: string;
static INDEX_JS_PLACEHOLDER: string;
static CODE_PUSH_APP_VERSION_PLACEHOLDER: string;
static CODE_PUSH_TEST_APP_NAME_PLACEHOLDER: string;
static CODE_PUSH_APP_ID_PLACEHOLDER: string;
static PLUGIN_VERSION_PLACEHOLDER: string;
/**
* Reads a command line option passed to mocha and returns a default if unspecified.
*/
static readMochaCommandLineOption(optionName: string, defaultValue?: string): string;
/**
* Reads command line options passed to mocha.
*/
static readMochaCommandLineFlag(optionName: string): boolean;
/**
* Executes a child process and returns a promise that resolves with its output or rejects with its error.
*/
static getProcessOutput(command: string, options?: {
cwd?: string;
stdio?: any;
customFds?: any;
env?: any;
encoding?: string;
timeout?: number;
maxBuffer?: number;
killSignal?: string;
noLogCommand?: boolean;
noLogStdOut?: boolean;
noLogStdErr?: boolean;
}): Q.Promise;
/**
* Returns the name of the plugin that is being tested.
*/
static getPluginName(): string;
/**
* Replaces a regex in a file with a given string.
*/
static replaceString(filePath: string, regex: string, replacement: string): void;
/**
* Copies a file from a given location to another.
*/
static copyFile(source: string, destination: string, overwrite: boolean): Q.Promise;
/**
* Archives the contents of sourceFolder and puts it in an archive at archivePath in targetFolder.
*/
static archiveFolder(sourceFolder: string, targetFolder: string, archivePath: string, isDiff: boolean): Q.Promise;
}
}
declare module 'code-push-plugin-testing-framework/script/index' {
import * as Platform from 'code-push-plugin-testing-framework/script/platform';
import * as PluginTestingFramework from 'code-push-plugin-testing-framework/script/test';
import { ProjectManager, setupTestRunScenario, setupUpdateScenario } from 'code-push-plugin-testing-framework/script/projectManager';
import * as ServerUtil from 'code-push-plugin-testing-framework/script/serverUtil';
import { TestBuilder } from 'code-push-plugin-testing-framework/script/testBuilder';
import * as TestConfig from 'code-push-plugin-testing-framework/script/testConfig';
import { TestUtil } from 'code-push-plugin-testing-framework/script/testUtil';
export { Platform, PluginTestingFramework, ProjectManager, setupTestRunScenario, setupUpdateScenario, ServerUtil, TestBuilder, TestConfig, TestUtil };
}
declare module 'code-push-plugin-testing-framework' {
import main = require('code-push-plugin-testing-framework/script/index');
export = main;
}
================================================
FILE: docs/api-android.md
================================================
### Java API Reference (Android)
### API for React Native 0.60 version and above
Since `autolinking` uses `react-native.config.js` to link plugins, constructors are specified in that file. But you can override custom variables to manage the CodePush plugin by placing these values in string resources.
* __Public Key__ - used for bundle verification in the Code Signing Feature. Please refer to [Code Signing](setup-android.md#code-signing-setup) section for more details about the Code Signing Feature.
To set the public key, you should add the content of the public key to `strings.xml` with name `CodePushPublicKey`. CodePush automatically gets this property and enables the Code Signing feature. For example:
```xml
your-public-key
```
* __Server Url__ - used for specifying CodePush Server Url.
The Default value: "https://codepush.appcenter.ms/" is overridden by adding your path to `strings.xml` with name `CodePushServerUrl`. CodePush automatically gets this property and will use this path to send requests. For example:
```xml
https://yourcodepush.server.com
```
### API for React Native lower than 0.60
The Java API is made available by importing the `com.microsoft.codepush.react.CodePush` class into your `MainActivity.java` file, and consists of a single public class named `CodePush`.
#### CodePush
Constructs the CodePush client runtime and represents the `ReactPackage` instance that you add to you app's list of packages.
##### Constructors
- __CodePush(String deploymentKey, Activity mainActivity)__ - Creates a new instance of the CodePush runtime, that will be used to query the service for updates via the provided deployment key. The `mainActivity` parameter should always be set to `this` when configuring your React packages list inside the `MainActivity` class. This constructor puts the CodePush runtime into "release mode", so if you want to enable debugging behavior, use the following constructor instead.
- __CodePush(String deploymentKey, Activity mainActivity, bool isDebugMode)__ - Equivalent to the previous constructor but allows you to specify whether you want the CodePush runtime to be in debug mode or not. When using this constructor, the `isDebugMode` parameter should always be set to `BuildConfig.DEBUG` in order to stay synchronized with your build type. When putting CodePush into debug mode, the following behaviors are enabled:
1. Old CodePush updates aren't deleted from storage whenever a new binary is deployed to the emulator/device. This behavior enables you to deploy new binaries, without bumping the version during development, and without continuously getting the same update every time your app calls `sync`.
2. The local cache that the React Native runtime maintains in debug mode is deleted whenever a CodePush update is installed. This ensures that when the app is restarted after an update is applied, you will see the expected changes. As soon as [this PR](https://github.com/facebook/react-native/pull/4738) is merged, we won't need to do this anymore.
- __CodePush(String deploymentKey, Context context, boolean isDebugMode, Integer publicKeyResourceDescriptor)__ - Equivalent to the previous constructor, but allows you to specify the public key resource descriptor needed to read public key content. Please refer to [Code Signing](setup-android.md#code-signing-setup) section for more details about the Code Signing Feature.
- __CodePush(String deploymentKey, Context context, boolean isDebugMode, String serverUrl)__ Constructor allows you to specify CodePush Server Url. The Default value: `"https://codepush.appcenter.ms/"` is overridden by value specified in `serverUrl`.
##### Builder
As an alternative to constructors *you can also use `CodePushBuilder`* to setup a CodePush instance configured with *only parameters you want*.
```java
@Override
protected List getPackages() {
return Arrays.asList(
new MainReactPackage(),
new CodePushBuilder("deployment-key-here",getApplicationContext())
.setIsDebugMode(BuildConfig.DEBUG)
.setPublicKeyResourceDescriptor(R.string.publicKey)
.setServerUrl("https://yourcodepush.server.com")
.build() //return configured CodePush instance
);
}
```
`CodePushBuilder` methods:
* __public CodePushBuilder(String deploymentKey, Context context)__ - setup same parameters as via __CodePush(String deploymentKey, Activity mainActivity)__
* __public CodePushBuilder setIsDebugMode(boolean isDebugMode)__ - allows you to specify whether you want the CodePush runtime to be in debug mode or not. Default value: `false`.
* __public CodePushBuilder setServerUrl(String serverUrl)__ - allows you to specify CodePush Server Url. Default value: `"https://codepush.appcenter.ms/"`.
* __public CodePushBuilder setPublicKeyResourceDescriptor(int publicKeyResourceDescriptor)__ - allows you to specify Public Key resource descriptor which will be used for reading Public Key content for `strings.xml` file. Please refer to [Code Signing](setup-android.md#code-signing-setup) section for more detailed information about purpose of this parameter.
* __public CodePush build()__ - return configured `CodePush` instance.
##### Public Methods
- __setDeploymentKey(String deploymentKey)__ - Sets the deployment key that the app should use when querying for updates. This is a dynamic alternative to setting the deployment key in Codepush constructor/builder and/or specifying a deployment key in JS when calling `checkForUpdate` or `sync`.
##### Static Methods
- __getBundleUrl()__ - Returns the path to the most recent version of your app's JS bundle file, assuming that the resource name is `index.android.bundle`. If your app is using a different bundle name, then use the overloaded version of this method which allows specifying it. This method has the same resolution behavior as the Objective-C equivalent described above.
- __getBundleUrl(String bundleName)__ - Returns the path to the most recent version of your app's JS bundle file, using the specified resource name (like `index.android.bundle`). This method has the same resolution behavior as the Objective-C equivalent described above.
- __getPackageFolder()__ - Returns the path to the current update folder.
- __overrideAppVersion(String appVersionOverride)__ - Sets the version of the application's binary interface, which would otherwise default to the Play Store version specified as the `versionName` in the `build.gradle`. This should be called a single time, before the CodePush instance is constructed.
================================================
FILE: docs/api-ios.md
================================================
### Objective-C API Reference (iOS)
The Objective-C API is made available by importing the `CodePush.h` header into your `AppDelegate.m` file, and consists of a single public class named `CodePush`.
#### CodePush
Contains static methods for retreiving the `NSURL` that represents the most recent JavaScript bundle file, and can be passed to the `RCTRootView`'s `initWithBundleURL` method when bootstrapping your app in the `AppDelegate.m` file.
The `CodePush` class' methods can be thought of as composite resolvers which always load the appropriate bundle, in order to accommodate the following scenarios:
1. When an end-user installs your app from the store (like `1.0.0`), they will get the JS bundle that is contained within the binary. This is the behavior you would get without using CodePush, but we make sure it doesn't break :)
2. As soon as you begin releasing CodePush updates, your end-users will get the JS bundle that represents the latest release for the configured deployment. This is the behavior that allows you to iterate beyond what you shipped to the store.
3. As soon as you release an update to the app store (like `1.1.0`), and your end-users update it, they will once again get the JS bundle that is contained within the binary. This behavior ensures that CodePush updates that targetted a previous binary version aren't used (since we don't know if they would work), and your end-users always have a working version of your app.
4. Repeat #2 and #3 as the CodePush releases and app store releases continue on into infinity (and beyond?)
Because of this behavior, you can safely deploy updates to both the app store(s) and CodePush as necesary, and rest assured that your end-users will always get the most recent version.
##### Methods
- __(NSURL \*)bundleURL__ - Returns the most recent JS bundle `NSURL` as described above. This method assumes that the name of the JS bundle contained within your app binary is `main.jsbundle`.
- __(NSURL \*)bundleURLForResource:(NSString \*)resourceName__ - Equivalent to the `bundleURL` method, but also allows customizing the name of the JS bundle that is looked for within the app binary. This is useful if you aren't naming this file `main` (which is the default convention). This method assumes that the JS bundle's extension is `*.jsbundle`.
- __(NSURL \*)bundleURLForResource:(NSString \*)resourceName withExtension:(NSString \*)resourceExtension__: Equivalent to the `bundleURLForResource:` method, but also allows customizing the extension used by the JS bundle that is looked for within the app binary. This is useful if you aren't naming this file `*.jsbundle` (which is the default convention).
- __(void)overrideAppVersion:(NSString \*)appVersionOverride__ - Sets the version of the application's binary interface, which would otherwise default to the App Store version specified as the `CFBundleShortVersionString` in the `Info.plist`. This should be called a single time, before the bundle URL is loaded.
- __(void)setDeploymentKey:(NSString \*)deploymentKey__ - Sets the deployment key that the app should use when querying for updates. This is a dynamic alternative to setting the deployment key in your `Info.plist` and/or specifying a deployment key in JS when calling `checkForUpdate` or `sync`.
================================================
FILE: docs/api-js.md
================================================
## API Reference
The CodePush plugin is made up of two components:
1. A JavaScript module, which can be imported/required, and allows the app to interact with the service during runtime (for example check for updates, inspect the metadata about the currently running app update).
2. A native API (Objective-C and Java) which allows the React Native app host to bootstrap itself with the right JS bundle location.
The following sections describe the shape and behavior of these APIs in detail:
### JavaScript API Reference
When you require `react-native-code-push`, the module object provides the following top-level methods in addition to the root-level [component decorator](#codepush):
* [allowRestart](#codepushallowrestart): Re-allows programmatic restarts to occur as a result of an update being installed, and optionally, immediately restarts the app if a pending update had attempted to restart the app while restarts were disallowed. This is an advanced API and is only necessary if your app explicitly disallowed restarts via the `disallowRestart` method.
* [checkForUpdate](#codepushcheckforupdate): Asks the CodePush service whether the configured app deployment has an update available.
* [disallowRestart](#codepushdisallowrestart): Temporarily disallows any programmatic restarts to occur as a result of a CodePush update being installed. This is an advanced API, and is useful when a component within your app (for example an onboarding process) needs to ensure that no end-user interruptions can occur during its lifetime.
* [getCurrentPackage](#codepushgetcurrentpackage): Retrieves the metadata about the currently installed update (like description, installation time, size). *NOTE: As of `v1.10.3-beta` of the CodePush module, this method is deprecated in favor of [`getUpdateMetadata`](#codepushgetupdatemetadata)*.
* [getUpdateMetadata](#codepushgetupdatemetadata): Retrieves the metadata for an installed update (like description, mandatory).
* [notifyAppReady](#codepushnotifyappready): Notifies the CodePush runtime that an installed update is considered successful. If you are manually checking for and installing updates (i.e. not using the [sync](#codepushsync) method to handle it all for you), then this method **MUST** be called; otherwise CodePush will treat the update as failed and rollback to the previous version when the app next restarts.
* [restartApp](#codepushrestartapp): Immediately restarts the app. If there is an update pending, it will be immediately displayed to the end user. Otherwise, calling this method simply has the same behavior as the end user killing and restarting the process.
* [sync](#codepushsync): Allows checking for an update, downloading it and installing it, all with a single call. Unless you need custom UI and/or behavior, we recommend most developers to use this method when integrating CodePush into their apps
* [clearUpdates](#clearupdates): Clear all downloaded CodePush updates. This is useful when switching to a different deployment which may have an older release than the current package.
_Note: we don’t recommend to use this method in scenarios other than that (CodePush will call this method automatically when needed in other cases) as it could lead to unpredictable behavior._
#### codePush
```javascript
// Wrapper function
codePush(rootComponent: React.Component): React.Component;
codePush(options: CodePushOptions)(rootComponent: React.Component): React.Component;
```
```javascript
// Decorator; Requires ES7 support
@codePush
@codePush(options: CodePushOptions)
```
Used to wrap a React component inside a "higher order" React component that knows how to synchronize your app's JavaScript bundle and image assets when it is mounted. Internally, the higher-order component calls [`sync`](#codepushsync) inside its `componentDidMount` lifecycle handle, which in turns performs an update check, downloads the update if it exists and installs the update for you.
This decorator provides support for letting you customize its behaviour to easily enable apps with different requirements. Below are some examples of ways you can use it (you can pick one or even use a combination):
1. **Silent sync on app start** *(the simplest, default behavior)*. Your app will automatically download available updates, and apply them the next time the app restarts (like the OS or end user killed it, or the device was restarted). This way, the entire update experience is "silent" to the end user, since they don't see any update prompt and/or "synthetic" app restarts.
```javascript
// Fully silent update which keeps the app in
// sync with the server, without ever
// interrupting the end user
class MyApp extends Component<{}> {}
MyApp = codePush(MyApp);
export default MyApp;
```
2. **Silent sync every time the app resumes**. Same as 1, except we check for updates, or apply an update if one exists every time the app returns to the foreground after being "backgrounded".
```javascript
// Sync for updates every time the app resumes.
class MyApp extends Component<{}> {}
MyApp = codePush({ checkFrequency: codePush.CheckFrequency.ON_APP_RESUME, installMode: codePush.InstallMode.ON_NEXT_RESUME })(MyApp);
export default MyApp;
```
3. **Interactive**. When an update is available, prompt the end user for permission before downloading it, and then immediately apply the update. If an update was released using the `mandatory` flag, the end user would still be notified about the update, but they wouldn't have the choice to ignore it.
```javascript
// Active update, which lets the end user know
// about each update, and displays it to them
// immediately after downloading it
class MyApp extends Component<{}> {}
MyApp = codePush({ updateDialog: true, installMode: codePush.InstallMode.IMMEDIATE })(MyApp);
export default MyApp;
```
4. **Log/display progress**. While the app is syncing with the server for updates, make use of the `codePushStatusDidChange` and/or `codePushDownloadDidProgress` event hooks to log down the different stages of this process, or even display a progress bar to the user.
```javascript
// Make use of the event hooks to keep track of
// the different stages of the sync process.
class MyApp extends Component<{}> {
codePushStatusDidChange(status) {
switch(status) {
case codePush.SyncStatus.CHECKING_FOR_UPDATE:
console.log("Checking for updates.");
break;
case codePush.SyncStatus.DOWNLOADING_PACKAGE:
console.log("Downloading package.");
break;
case codePush.SyncStatus.INSTALLING_UPDATE:
console.log("Installing update.");
break;
case codePush.SyncStatus.UP_TO_DATE:
console.log("Up-to-date.");
break;
case codePush.SyncStatus.UPDATE_INSTALLED:
console.log("Update installed.");
break;
}
}
codePushDownloadDidProgress(progress) {
console.log(progress.receivedBytes + " of " + progress.totalBytes + " received.");
}
}
MyApp = codePush(MyApp);
export default MyApp;
```
##### CodePushOptions
The `codePush` decorator accepts an "options" object that allows you to customize numerous aspects of the default behavior mentioned above:
* __checkFrequency__ *(codePush.CheckFrequency)* - Specifies when you would like to check for updates. Defaults to `codePush.CheckFrequency.ON_APP_START`. Refer to the [`CheckFrequency`](#checkfrequency) enum reference for a description of the available options and what they do.
* __deploymentKey__ *(String)* - Specifies the deployment key you want to query for an update against. By default, this value is derived from the `Info.plist` file (iOS) and `MainActivity.java` file (Android), but this option allows you to override it from the script-side if you need to dynamically use a different deployment.
* __installMode__ *(codePush.InstallMode)* - Specifies when you would like to install optional updates (i.e. those that aren't marked as mandatory). Defaults to `codePush.InstallMode.ON_NEXT_RESTART`. Refer to the [`InstallMode`](#installmode) enum reference for a description of the available options and what they do.
* __mandatoryInstallMode__ *(codePush.InstallMode)* - Specifies when you would like to install updates which are marked as mandatory. Defaults to `codePush.InstallMode.IMMEDIATE`. Refer to the [`InstallMode`](#installmode) enum reference for a description of the available options and what they do.
* __minimumBackgroundDuration__ *(Number)* - Specifies the minimum number of seconds that the app needs to have been in the background before restarting the app. This property only applies to updates which are installed using `InstallMode.ON_NEXT_RESUME` or `InstallMode.ON_NEXT_SUSPEND`, and can be useful for getting your update in front of end users sooner, without being too obtrusive. Defaults to `0`, which has the effect of applying the update immediately after a resume or unless the app suspension is long enough to not matter, regardless how long it was in the background.
* __updateDialog__ *(UpdateDialogOptions)* - An "options" object used to determine whether a confirmation dialog should be displayed to the end user when an update is available, and if so, what strings to use. Defaults to `null`, which has the effect of disabling the dialog completely. Setting this to any truthy value will enable the dialog with the default strings, and passing an object to this parameter allows enabling the dialog as well as overriding one or more of the default strings. Before enabling this option within an App Store-distributed app, please refer to [this note](https://github.com/microsoft/react-native-code-push#app-store).
The following list represents the available options and their defaults:
* __appendReleaseDescription__ *(Boolean)* - Indicates whether you would like to append the description of an available release to the notification message which is displayed to the end user. Defaults to `false`.
* __descriptionPrefix__ *(String)* - Indicates the string you would like to prefix the release description with, if any, when displaying the update notification to the end user. Defaults to `" Description: "`
* __mandatoryContinueButtonLabel__ *(String)* - The text to use for the button the end user must press in order to install a mandatory update. Defaults to `"Continue"`.
* __mandatoryUpdateMessage__ *(String)* - The text used as the body of an update notification, when the update is specified as mandatory. Defaults to `"An update is available that must be installed."`.
* __optionalIgnoreButtonLabel__ *(String)* - The text to use for the button the end user can press in order to ignore an optional update that is available. Defaults to `"Ignore"`.
* __optionalInstallButtonLabel__ *(String)* - The text to use for the button the end user can press in order to install an optional update. Defaults to `"Install"`.
* __optionalUpdateMessage__ *(String)* - The text used as the body of an update notification, when the update is optional. Defaults to `"An update is available. Would you like to install it?"`.
* __title__ *(String)* - The text used as the header of an update notification that is displayed to the end user. Defaults to `"Update available"`.
* __rollbackRetryOptions__ *(RollbackRetryOptions)* - The rollback retry mechanism allows the application to attempt to reinstall an update that was previously rolled back (with the restrictions specified in the options). It is an "options" object used to determine whether a rollback retry should occur, and if so, what settings to use for the rollback retry. This defaults to null, which has the effect of disabling the retry mechanism. Setting this to any truthy value will enable the retry mechanism with the default settings, and passing an object to this parameter allows enabling the rollback retry as well as overriding one or more of the default values.
The following list represents the available options and their defaults:
* __delayInHours__ *(Number)* - Specifies the minimum time in hours that the app will wait after the latest rollback before attempting to reinstall the same rolled-back package. Defaults to `24`.
* __maxRetryAttempts__ *(Number)* - Specifies the maximum number of retry attempts that the app can make before it stops trying. Cannot be less than `1`. Defaults to `1`.
##### codePushStatusDidChange (event hook)
Called when the sync process moves from one stage to another in the overall update process. The event hook is called with a status code which represents the current state, and can be any of the [`SyncStatus`](#syncstatus) values.
##### codePushDownloadDidProgress (event hook)
Called periodically when an available update is being downloaded from the CodePush server. The method is called with a `DownloadProgress` object, which contains the following two properties:
* __totalBytes__ *(Number)* - The total number of bytes expected to be received for this update (i.e. the size of the set of files which changed from the previous release).
* __receivedBytes__ *(Number)* - The number of bytes downloaded thus far, which can be used to track download progress.
#### codePush.allowRestart
```javascript
codePush.allowRestart(): void;
```
Re-allows programmatic restarts to occur, that would have otherwise been rejected due to a previous call to `disallowRestart`. If `disallowRestart` was never called in the first place, then calling this method will simply result in a no-op.
If a CodePush update is currently pending, which attempted to restart the app (for example it used `InstallMode.IMMEDIATE`), but was blocked due to `disallowRestart` having been called, then calling `allowRestart` will result in an immediate restart. This allows the update to be applied as soon as possible, without interrupting the end user during critical workflows (for example an onboarding process).
For example, calling `allowRestart` would trigger an immediate restart if either of the three scenarios mentioned in the [`disallowRestart` docs](#codepushdisallowrestart) occured after `disallowRestart` was called. However, calling `allowRestart` wouldn't trigger a restart if the following were true:
1. No CodePush updates were installed since the last time `disallowRestart` was called, and therefore, there isn't any need to restart anyways.
2. There is currently a pending CodePush update, but it was installed via `InstallMode.ON_NEXT_RESTART`, and therefore, doesn't require a programmatic restart.
3. There is currently a pending CodePush update, but it was installed via `InstallMode.ON_NEXT_RESUME` and the app hasn't been put into the background yet, and therefore, there isn't a need to programmatically restart yet.
4. No calls to `restartApp` were made since the last time `disallowRestart` was called.
This behavior ensures that no restarts will be triggered as a result of calling `allowRestart` unless one was explictly requested during the disallowed period. In this way, `allowRestart` is somewhat similar to calling `restartApp(true)`, except the former will only trigger a restart if the currently pending update wanted to restart, whereas the later would restart as long as an update is pending.
See [disallowRestart](#codepushdisallowrestart) for an example of how this method can be used.
#### codePush.checkForUpdate
```javascript
codePush.checkForUpdate(deploymentKey: String = null, handleBinaryVersionMismatchCallback: (update: RemotePackage) => void): Promise;
```
Queries the CodePush service to see whether the configured app deployment has an update available. By default, it will use the deployment key that is configured in your `Info.plist` file (iOS), or `MainActivity.java` file (Android), but you can override that by specifying a value via the optional `deploymentKey` parameter. This can be useful when you want to dynamically "redirect" a user to a specific deployment, such as allowing "early access" via an easter egg or a user setting switch.
Second optional parameter `handleBinaryVersionMismatchCallback` is an optional callback function that can be used to notify user if there are any binary update.
E.g. consider a use-case where currently installed binary version is 1.0.1 with label(codepush label) v1. Later native code was changed in the dev cycle and binary version was updated to 1.0.2. When code-push update check is triggered we ignore updates having binary version mismatch (because the update is not targeting to the binary version of currently installed app). In this case installed app (1.0.1) will ignore the update targeting version 1.0.2. You can use `handleBinaryVersionMismatchCallback` to provide a hook to handle such situations.
**NOTE:**
Be cautious to use Alerts within this callback if you are developing iOS application, due to [App Store](https://developer.apple.com/app-store/review/guidelines/) review process:
> Apps must not force users to rate the app, review the app, download other apps, or other similar actions in order to access functionality, content, or use of the app.
This method returns a `Promise` which resolves to one of two possible values:
1. `null` if there is no update available. This can occur in the following scenarios:
1. The configured deployment doesn't contain any releases, and therefore, nothing to update.
2. The latest release within the configured deployment is targeting a different binary version than what you're currently running (either older or newer).
3. The currently running app already has the latest release from the configured deployment, and therefore, doesn't need it again.
4. The latest release within the configured deployment is currently marked as disabled, and therefore, isn't allowed to be downloaded.
5. The latest release within the configured deployment is in an "active rollout" state, and the requesting device doesn't fall within the percentage of users who are eligible for it.
2. A [`RemotePackage`](#remotepackage) instance which represents an available update that can be inspected and/or subsequently downloaded.
Example Usage:
```javascript
codePush.checkForUpdate()
.then((update) => {
if (!update) {
console.log("The app is up to date!");
} else {
console.log("An update is available! Should we download it?");
}
});
```
#### codePush.disallowRestart
```javascript
codePush.disallowRestart(): void;
```
Temporarily disallows programmatic restarts to occur as a result of either of following scenarios:
1. A CodePush update is installed using `InstallMode.IMMEDIATE`
2. A CodePush update is installed using `InstallMode.ON_NEXT_RESUME` and the app is resumed from the background (optionally being throttled by the `minimumBackgroundDuration` property)
3. The `restartApp` method was called
*NOTE: #1 and #2 effectively work by calling `restartApp` for you, so you can think of `disallowRestart` as blocking any call to `restartApp`, regardless if your app calls it directly or indirectly.*
After calling this method, any calls to `sync` would still be allowed to check for an update, download it and install it, but an attempt to restart the app would be queued until `allowRestart` is called. This way, the restart request is captured and can be "flushed" whenever you want to allow it to occur.
This is an advanced API, and is primarily useful when individual components within your app (like an onboarding process) need to ensure that no end-user interruptions can occur during their lifetime, while continuing to allow the app to keep syncing with the CodePush server at its own pace and using whatever install modes are appropriate. This has the benefit of allowing the app to discover and download available updates as soon as possible, while also preventing any disruptions during key end-user experiences.
As an alternative, you could also choose to simply use `InstallMode.ON_NEXT_RESTART` whenever calling `sync` (which will never attempt to programmatically restart the app), and then explicity calling `restartApp` at points in your app that you know it is "safe" to do so. `disallowRestart` provides an alternative approach to this when the code that synchronizes with the CodePush server is separate from the code/components that want to enforce a no-restart policy.
Example Usage:
```javascript
class OnboardingProcess extends Component {
...
componentWillMount() {
// Ensure that any CodePush updates which are
// synchronized in the background can't trigger
// a restart while this component is mounted.
codePush.disallowRestart();
}
componentWillUnmount() {
// Reallow restarts, and optionally trigger
// a restart if one was currently pending.
codePush.allowRestart();
}
...
}
```
#### codePush.getCurrentPackage
*NOTE: This method is considered deprecated as of `v1.10.3-beta` of the CodePush module. If you're running this version (or newer), we would recommend using the [`codePush.getUpdateMetadata`](#codepushgetupdatemetadata) instead, since it has more predictable behavior.*
```javascript
codePush.getCurrentPackage(): Promise;
```
Retrieves the metadata about the currently installed "package" (like description, installation time). This can be useful for scenarios such as displaying a "what's new?" dialog after an update has been applied or checking whether there is a pending update that is waiting to be applied via a resume or restart.
This method returns a `Promise` which resolves to one of two possible values:
1. `null` if the app is currently running the JS bundle from the binary and not a CodePush update. This occurs in the following scenarios:
1. The end-user installed the app binary and has yet to install a CodePush update
1. The end-user installed an update of the binary (for example from the store), which cleared away the old CodePush updates, and gave precedence back to the JS binary in the binary.
2. A [`LocalPackage`](#localpackage) instance which represents the metadata for the currently running CodePush update.
Example Usage:
```javascript
codePush.getCurrentPackage()
.then((update) => {
// If the current app "session" represents the first time
// this update has run, and it had a description provided
// with it upon release, let's show it to the end user
if (update.isFirstRun && update.description) {
// Display a "what's new?" modal
}
});
```
#### codePush.getUpdateMetadata
```javascript
codePush.getUpdateMetadata(updateState: UpdateState = UpdateState.RUNNING): Promise;
```
Retrieves the metadata for an installed update (for example description, mandatory) whose state matches the specified `updateState` parameter. This can be useful for scenarios such as displaying a "what's new?" dialog after an update has been applied or checking whether there is a pending update that is waiting to be applied via a resume or restart. For more details about the possible update states, and what they represent, refer to the [UpdateState reference](#updatestate).
This method returns a `Promise` which resolves to one of two possible values:
1. `null` if an update with the specified state doesn't currently exist. This occurs in the following scenarios:
1. The end-user hasn't installed any CodePush updates yet, and therefore, no metadata is available for any updates, regardless what you specify as the `updateState` parameter.
2. The end-user installed an update of the binary (for example from the store), which cleared away the old CodePush updates, and gave precedence back to the JS binary in the binary. Therefore, it would exhibit the same behavior as #1
3. The `updateState` parameter is set to `UpdateState.RUNNING`, but the app isn't currently running a CodePush update. There may be a pending update, but the app hasn't been restarted yet in order to make it active.
4. The `updateState` parameter is set to `UpdateState.PENDING`, but the app doesn't have any currently pending updates.
2. A [`LocalPackage`](#localpackage) instance which represents the metadata for the currently requested CodePush update (either the running or pending).
Example Usage:
```javascript
// Check if there is currently a CodePush update running, and if
// so, register it with the HockeyApp SDK (https://github.com/slowpath/react-native-hockeyapp)
// so that crash reports will correctly display the JS bundle version the user was running.
codePush.getUpdateMetadata().then((update) => {
if (update) {
hockeyApp.addMetadata({ CodePushRelease: update.label });
}
});
// Check to see if there is still an update pending.
codePush.getUpdateMetadata(UpdateState.PENDING).then((update) => {
if (update) {
// There's a pending update, do we want to force a restart?
}
});
```
#### codePush.notifyAppReady
```javascript
codePush.notifyAppReady(): Promise;
```
Notifies the CodePush runtime that a freshly installed update should be considered successful, and therefore, an automatic client-side rollback isn't necessary. It is mandatory to call this function somewhere in the code of the updated bundle. Otherwise, when the app next restarts, the CodePush runtime will assume that the installed update has failed and roll back to the previous version. This behavior exists to help ensure that your end users aren't blocked by a broken update.
If you are using the `sync` function, and doing your update check on app start, then you don't need to manually call `notifyAppReady` since `sync` will call it for you. This behavior exists due to the assumption that the point at which `sync` is called in your app represents a good approximation of a successful startup.
*NOTE: This method is also aliased as `notifyApplicationReady` (for backwards compatibility).*
#### codePush.restartApp
```javascript
codePush.restartApp(onlyIfUpdateIsPending: Boolean = false): void;
```
Immediately restarts the app. If a truthy value is provided to the `onlyIfUpdateIsPending` parameter, then the app will only restart if there is actually a pending update waiting to be applied.
This method is for advanced scenarios, and is primarily useful when the following conditions are true:
1. Your app is specifying an install mode value of `ON_NEXT_RESTART` or `ON_NEXT_RESUME` when calling the `sync` or `LocalPackage.install` methods. This has the effect of not applying your update until the app has been restarted (by either the end user or OS) or resumed, and therefore, the update won't be immediately displayed to the end user.
2. You have an app-specific user event (like the end user navigated back to the app's home route) that allows you to apply the update in an unobtrusive way, and potentially gets the update in front of the end user sooner then waiting until the next restart or resume.
#### codePush.sync
```javascript
codePush.sync(options: Object, syncStatusChangeCallback: function(syncStatus: Number), downloadProgressCallback: function(progress: DownloadProgress), handleBinaryVersionMismatchCallback: function(update: RemotePackage)): Promise;
```
Synchronizes your app's JavaScript bundle and image assets with the latest release to the configured deployment. Unlike the [checkForUpdate](#codepushcheckforupdate) method, which simply checks for the presence of an update, and let's you control what to do next, `sync` handles the update check, download and installation experience for you.
This method provides support for two different (but customizable) "modes" to easily enable apps with different requirements:
1. **Silent mode** *(the default behavior)*, which automatically downloads available updates, and applies them the next time the app restarts (for example the OS or end user killed it, or the device was restarted). This way, the entire update experience is "silent" to the end user, since they don't see any update prompt and/or "synthetic" app restarts.
2. **Active mode**, which when an update is available, prompts the end user for permission before downloading it, and then immediately applies the update. If an update was released using the `mandatory` flag, the end user would still be notified about the update, but they wouldn't have the choice to ignore it.
Example Usage:
```javascript
// Fully silent update which keeps the app in
// sync with the server, without ever
// interrupting the end user
codePush.sync();
// Active update, which lets the end user know
// about each update, and displays it to them
// immediately after downloading it
codePush.sync({ updateDialog: true, installMode: codePush.InstallMode.IMMEDIATE });
```
*Note: If you want to decide whether you check and/or download an available update based on the end user's device battery level, network conditions, etc. then simply wrap the call to `sync` in a condition that ensures you only call it when desired.*
##### SyncOptions
While the `sync` method tries to make it easy to perform silent and active updates with little configuration, it accepts an "options" object that allows you to customize numerous aspects of the default behavior mentioned above. The options available are identical to the [CodePushOptions](#codepushoptions), with the exception of the `checkFrequency` option:
* __deploymentKey__ *(String)* - Refer to [`CodePushOptions`](#codepushoptions).
* __installMode__ *(codePush.InstallMode)* - Refer to [`CodePushOptions`](#codepushoptions).
* __mandatoryInstallMode__ *(codePush.InstallMode)* - Refer to [`CodePushOptions`](#codepushoptions).
* __minimumBackgroundDuration__ *(Number)* - Refer to [`CodePushOptions`](#codepushoptions).
* __updateDialog__ *(UpdateDialogOptions)* - Refer to [`CodePushOptions`](#codepushoptions).
Example Usage:
```javascript
// Use a different deployment key for this
// specific call, instead of the one configured
// in the Info.plist file
codePush.sync({ deploymentKey: "KEY" });
// Download the update silently, but install it on
// the next resume, as long as at least 5 minutes
// has passed since the app was put into the background.
codePush.sync({ installMode: codePush.InstallMode.ON_NEXT_RESUME, minimumBackgroundDuration: 60 * 5 });
// Download the update silently, and install optional updates
// on the next restart, but install mandatory updates on the next resume.
codePush.sync({ mandatoryInstallMode: codePush.InstallMode.ON_NEXT_RESUME });
// Changing the title displayed in the
// confirmation dialog of an "active" update
codePush.sync({ updateDialog: { title: "An update is available!" } });
// Displaying an update prompt which includes the
// description associated with the CodePush release
codePush.sync({
updateDialog: {
appendReleaseDescription: true,
descriptionPrefix: "\n\nChange log:\n"
},
installMode: codePush.InstallMode.IMMEDIATE
});
```
In addition to the options, the `sync` method also accepts several optional function parameters which allow you to subscribe to the lifecycle of the `sync` "pipeline" in order to display additional UI as needed (like a "checking for update modal or a download progress modal):
* __syncStatusChangedCallback__ *((syncStatus: Number) => void)* - Called when the sync process moves from one stage to another in the overall update process. The method is called with a status code which represents the current state, and can be any of the [`SyncStatus`](#syncstatus) values.
* __downloadProgressCallback__ *((progress: DownloadProgress) => void)* - Called periodically when an available update is being downloaded from the CodePush server. The method is called with a `DownloadProgress` object, which contains the following two properties:
* __totalBytes__ *(Number)* - The total number of bytes expected to be received for this update (i.e. the size of the set of files which changed from the previous release).
* __receivedBytes__ *(Number)* - The number of bytes downloaded thus far, which can be used to track download progress.
* __handleBinaryVersionMismatchCallback__ *((update: RemotePackage) => void)* -
Called when there are any binary update available. The method is called with a [`RemotePackage`](#remotepackage) object. Refer to [codePush.checkForUpdate](#codepushcheckforupdate) section for more details.
Example Usage:
```javascript
// Prompt the user when an update is available
// and then display a "downloading" modal
codePush.sync({ updateDialog: true },
(status) => {
switch (status) {
case codePush.SyncStatus.DOWNLOADING_PACKAGE:
// Show "downloading" modal
break;
case codePush.SyncStatus.INSTALLING_UPDATE:
// Hide "downloading" modal
break;
}
},
({ receivedBytes, totalBytes, }) => {
/* Update download modal progress */
}
);
```
This method returns a `Promise` which is resolved to a `SyncStatus` code that indicates why the `sync` call succeeded. This code can be one of the following `SyncStatus` values:
* __codePush.SyncStatus.UP_TO_DATE__ *(0)* - The app is up-to-date with the CodePush server.
* __codePush.SyncStatus.UPDATE_IGNORED__ *(2)* - The app had an optional update which the end user chose to ignore. (This is only applicable when the `updateDialog` is used)
* __codePush.SyncStatus.UPDATE_INSTALLED__ *(1)* - The update has been installed and will be run either immediately after the `syncStatusChangedCallback` function returns or the next time the app resumes/restarts, depending on the `InstallMode` specified in `SyncOptions`.
* __codePush.SyncStatus.SYNC_IN_PROGRESS__ *(4)* - There is an ongoing `sync` operation running which prevents the current call from being executed.
The `sync` method can be called anywhere you'd like to check for an update. That could be in the `componentWillMount` lifecycle event of your root component, the onPress handler of a `` component, in the callback of a periodic timer, or whatever else makes sense for your needs. Just like the `checkForUpdate` method, it will perform the network request to check for an update in the background, so it won't impact your UI thread and/or JavaScript thread's responsiveness.
#### Package objects
The `checkForUpdate` and `getUpdateMetadata` methods return `Promise` objects, that when resolved, provide acces to "package" objects. The package represents your code update as well as any extra metadata (like description, mandatory?). The CodePush API has the distinction between the following types of packages:
* [LocalPackage](#localpackage): Represents a downloaded update that is either already running, or has been installed and is pending an app restart.
* [RemotePackage](#remotepackage): Represents an available update on the CodePush server that hasn't been downloaded yet.
##### LocalPackage
Contains details about an update that has been downloaded locally or already installed. You can get a reference to an instance of this object either by calling the module-level `getUpdateMetadata` method, or as the value of the promise returned by the `RemotePackage.download` method.
###### Properties
- __appVersion__: The app binary version that this update is dependent on. This is the value that was specified via the `appStoreVersion` parameter when calling the CLI's `release` command. *(String)*
- __deploymentKey__: The deployment key that was used to originally download this update. *(String)*
- __description__: The description of the update. This is the same value that you specified in the CLI when you released the update. *(String)*
- __failedInstall__: Indicates whether this update has been previously installed but was rolled back. The `sync` method will automatically ignore updates which have previously failed, so you only need to worry about this property if using `checkForUpdate`. *(Boolean)*
- __isFirstRun__: Indicates whether this is the first time the update has been run after being installed. This is useful for determining whether you would like to show a "What's New?" UI to the end user after installing an update. *(Boolean)*
- __isMandatory__: Indicates whether the update is considered mandatory. This is the value that was specified in the CLI when the update was released. *(Boolean)*
- __isPending__: Indicates whether this update is in a "pending" state. When `true`, that means the update has been downloaded and installed, but the app restart needed to apply it hasn't occurred yet, and therefore, it's changes aren't currently visible to the end-user. *(Boolean)*
- __label__: The internal label automatically given to the update by the CodePush server, such as `v5`. This value uniquely identifies the update within it's deployment. *(String)*
- __packageHash__: The SHA hash value of the update. *(String)*
- __packageSize__: The size of the code contained within the update, in bytes. *(Number)*
###### Methods
- __install(installMode: codePush.InstallMode = codePush.InstallMode.ON_NEXT_RESTART, minimumBackgroundDuration = 0): Promise<void>__: Installs the update by saving it to the location on disk where the runtime expects to find the latest version of the app. The `installMode` parameter controls when the changes are actually presented to the end user. The default value is to wait until the next app restart to display the changes, but you can refer to the [`InstallMode`](#installmode) enum reference for a description of the available options and what they do. If the `installMode` parameter is set to `InstallMode.ON_NEXT_RESUME`, then the `minimumBackgroundDuration` parameter allows you to control how long the app must have been in the background before forcing the install after it is resumed.
##### RemotePackage
Contains details about an update that is available for download from the CodePush server. You get a reference to an instance of this object by calling the `checkForUpdate` method when an update is available. If you are using the `sync` API, you don't need to worry about the `RemotePackage`, since it will handle the download and installation process automatically for you.
###### Properties
The `RemotePackage` inherits all of the same properties as the `LocalPackage`, but includes one additional one:
- __downloadUrl__: The URL at which the package is available for download. This property is only needed for advanced usage, since the `download` method will automatically handle the acquisition of updates for you. *(String)*
###### Methods
- __download(downloadProgressCallback?: Function): Promise<LocalPackage>__: Downloads the available update from the CodePush service. If a `downloadProgressCallback` is specified, it will be called periodically with a `DownloadProgress` object (`{ totalBytes: Number, receivedBytes: Number }`) that reports the progress of the download until it completes. Returns a Promise that resolves with the `LocalPackage`.
#### Enums
The CodePush API includes the following enums which can be used to customize the update experience:
##### InstallMode
This enum specifies when you would like an installed update to actually be applied, and can be passed to either the `sync` or `LocalPackage.install` methods. It includes the following values:
* __codePush.InstallMode.IMMEDIATE__ *(0)* - Indicates that you want to install the update and restart the app immediately. This value is appropriate for debugging scenarios as well as when displaying an update prompt to the user, since they would expect to see the changes immediately after accepting the installation. Additionally, this mode can be used to enforce mandatory updates, since it removes the potentially undesired latency between the update installation and the next time the end user restarts or resumes the app.
* __codePush.InstallMode.ON_NEXT_RESTART__ *(1)* - Indicates that you want to install the update, but not forcibly restart the app. When the app is "naturally" restarted (due the OS or end user killing it), the update will be seamlessly picked up. This value is appropriate when performing silent updates, since it would likely be disruptive to the end user if the app suddenly restarted out of nowhere, since they wouldn't have realized an update was even downloaded. This is the default mode used for both the `sync` and `LocalPackage.install` methods.
* __codePush.InstallMode.ON_NEXT_RESUME__ *(2)* - Indicates that you want to install the update, but don't want to restart the app until the next time the end user resumes it from the background. This way, you don't disrupt their current session, but you can get the update in front of them sooner then having to wait for the next natural restart. This value is appropriate for silent installs that can be applied on resume in a non-invasive way.
* __codePush.InstallMode.ON_NEXT_SUSPEND__ *(3)* - Indicates that you want to install the update _while_ it is in the background, but only after it has been in the background for `minimumBackgroundDuration` seconds (0 by default), so that user context isn't lost unless the app suspension is long enough to not matter.
##### CheckFrequency
This enum specifies when you would like your app to sync with the server for updates, and can be passed to the `codePushify` decorator. It includes the following values:
* __codePush.CheckFrequency.ON_APP_START__ *(0)* - Indicates that you want to check for updates whenever the app's process is started.
* __codePush.CheckFrequency.ON_APP_RESUME__ *(1)* - Indicates that you want to check for updates whenever the app is brought back to the foreground after being "backgrounded" (user pressed the home button, app launches a seperate payment process, etc).
* __codePush.CheckFrequency.MANUAL__ *(2)* - Disable automatic checking for updates, but only check when [`codePush.sync()`](#codepushsync) is called in app code.
##### SyncStatus
This enum is provided to the `syncStatusChangedCallback` function that can be passed to the `sync` method, in order to hook into the overall update process. It includes the following values:
* __codePush.SyncStatus.UP_TO_DATE__ *(0)* - The app is fully up-to-date with the configured deployment.
* __codePush.SyncStatus.UPDATE_INSTALLED__ *(1)* - An available update has been installed and will be run either immediately after the `syncStatusChangedCallback` function returns or the next time the app resumes/restarts, depending on the `InstallMode` specified in `SyncOptions`.
* __codePush.SyncStatus.UPDATE_IGNORED__ *(2)* - The app has an optional update, which the end user chose to ignore. (This is only applicable when the `updateDialog` is used)
* __codePush.SyncStatus.UNKNOWN_ERROR__ *(3)* - The sync operation encountered an unknown error.
* __codePush.SyncStatus.SYNC_IN_PROGRESS__ *(4)* - There is an ongoing `sync` operation running which prevents the current call from being executed.
* __codePush.SyncStatus.CHECKING_FOR_UPDATE__ *(5)* - The CodePush server is being queried for an update.
* __codePush.SyncStatus.AWAITING_USER_ACTION__ *(6)* - An update is available, and a confirmation dialog was shown to the end user. (This is only applicable when the `updateDialog` is used)
* __codePush.SyncStatus.DOWNLOADING_PACKAGE__ *(7)* - An available update is being downloaded from the CodePush server.
* __codePush.SyncStatus.INSTALLING_UPDATE__ *(8)* - An available update was downloaded and is about to be installed.
##### UpdateState
This enum specifies the state that an update is currently in, and can be specified when calling the `getUpdateMetadata` method. It includes the following values:
* __codePush.UpdateState.RUNNING__ *(0)* - Indicates that an update represents the version of the app that is currently running. This can be useful for identifying attributes about the app, for scenarios such as displaying the release description in a "what's new?" dialog or reporting the latest version to an analytics and/or crash reporting service.
* __codePush.UpdateState.PENDING__ *(1)* - Indicates than an update has been installed, but the app hasn't been restarted yet in order to apply it. This can be useful for determining whether there is a pending update, which you may want to force a programmatic restart (via `restartApp`) in order to apply.
* __codePush.UpdateState.LATEST__ *(2)* - Indicates than an update represents the latest available release, and can be either currently running or pending.
================================================
FILE: docs/multi-deployment-testing-android.md
================================================
### Android
> NOTE
>
> Complete demo configured with "multi-deployment testing" feature is [here](https://github.com/microsoft/react-native-code-push/files/1314118/rncp1004.zip).
The [Android Gradle plugin](https://google.github.io/android-gradle-dsl/current/index.html) allows you to define custom config settings for each "build type" (like debug, release). This mechanism allows you to easily configure your debug builds to use your CodePush staging deployment key and your release builds to use your CodePush production deployment key.
*NOTE: As a reminder, you can retrieve these keys by running `appcenter codepush deployment list -a / -k` from your terminal.*
To set this up, perform the following steps:
**For React Native >= v0.60**
1. Open the project's app level `build.gradle` file (for example `android/app/build.gradle` in standard React Native projects)
2. Find the `android { buildTypes {} }` section and define `resValue` entries for both your `debug` and `release` build types, which reference your `Staging` and `Production` deployment keys respectively.
```groovy
android {
...
buildTypes {
debug {
...
// Note: CodePush updates should not be tested in Debug mode as they are overriden by the RN packager. However, because CodePush checks for updates in all modes, we must supply a key.
resValue "string", "CodePushDeploymentKey", '""'
...
}
releaseStaging {
...
resValue "string", "CodePushDeploymentKey", '""'
// Note: It is a good idea to provide matchingFallbacks for the new buildType you create to prevent build issues
// Add the following line if not already there
matchingFallbacks = ['release']
...
}
release {
...
resValue "string", "CodePushDeploymentKey", '""'
...
}
}
...
}
```
*NOTE: Remember to remove the key from `strings.xml` if you are configuring the deployment key in the build process*
*NOTE: The naming convention for `releaseStaging` is significant due to [this line](https://github.com/facebook/react-native/blob/e083f9a139b3f8c5552528f8f8018529ef3193b9/react.gradle#L79).*
**For React Native v0.29 - v0.59**
1. Open up your `MainApplication.java` file and make the following changes:
```java
@Override
protected List getPackages() {
return Arrays.asList(
...
new CodePush(BuildConfig.CODEPUSH_KEY, MainApplication.this, BuildConfig.DEBUG), // Add/change this line.
...
);
}
```
2. Open your app's `build.gradle` file (for example `android/app/build.gradle` in standard React Native projects)
3. Find the `android { buildTypes {} }` section and define `buildConfigField` entries for both your `debug` and `release` build types, which reference your `Staging` and `Production` deployment keys respectively. If you prefer, you can define the key literals in your `gradle.properties` file, and then reference them here. Either way will work, and it's just a matter of personal preference.
```groovy
android {
...
buildTypes {
debug {
...
// Note: CodePush updates should not be tested in Debug mode as they are overriden by the RN packager. However, because CodePush checks for updates in all modes, we must supply a key.
buildConfigField "String", "CODEPUSH_KEY", '""'
...
}
releaseStaging {
...
buildConfigField "String", "CODEPUSH_KEY", '""'
// Note: It is a good idea to provide matchingFallbacks for the new buildType you create to prevent build issues
// Add the following line if not already there
matchingFallbacks = ['release']
...
}
release {
...
buildConfigField "String", "CODEPUSH_KEY", '""'
...
}
}
...
}
```
*NOTE: The naming convention for `releaseStaging` is significant due to [this line](https://github.com/facebook/react-native/blob/e083f9a139b3f8c5552528f8f8018529ef3193b9/react.gradle#L79).*
4. Pass the deployment key to the `CodePush` constructor via the build config you defined, as opposed to a string literal.
**For React Native v0.19 - v0.28**
Open up your `MainActivity.java` file and make the following changes:
```java
@Override
protected List getPackages() {
return Arrays.asList(
...
new CodePush(BuildConfig.CODEPUSH_KEY, this, BuildConfig.DEBUG), // Add/change this line.
...
);
}
```
*Note: If you gave your build setting a different name in your Gradle file, simply make sure to reflect that in your Java code.*
And that's it! Now when you run or build your app, your debug builds will automatically be configured to sync with your `Staging` deployment, and your release builds will be configured to sync with your `Production` deployment.
*NOTE: By default, the `react-native run-android` command builds and deploys the debug version of your app, so if you want to test out a release/production build, simply run `react-native run-android --variant release. Refer to the [React Native docs](http://facebook.github.io/react-native/docs/signed-apk-android.html#conten) for details about how to configure and create release builds for your Android apps.*
If you want to be able to install both debug and release builds simultaneously on the same device (highly recommended!), then you need to ensure that your debug build has a unique identity and icon from your release build. Otherwise, neither the OS nor you will be able to differentiate between the two. You can achieve this by performing the following steps:
1. In your `build.gradle` file, specify the [`applicationIdSuffix`](http://google.github.io/android-gradle-dsl/current/com.android.build.gradle.internal.dsl.BuildType.html#com.android.build.gradle.internal.dsl.BuildType:applicationIdSuffix) field for your debug build type, which gives your debug build a unique identity for the OS (like `com.foo` vs. `com.foo.debug`).
```groovy
buildTypes {
debug {
applicationIdSuffix ".debug"
}
}
```
2. Create the `app/src/debug/res` directory structure in your app, which allows overriding resources (like strings, icons, layouts) for your debug builds
3. Create a `values` directory underneath the debug res directory created in #2, and copy the existing `strings.xml` file from the `app/src/main/res/values` directory
4. Open up the new debug `strings.xml` file and change the `` element's value to something else (like `foo-debug`). This ensures that your debug build now has a distinct display name, so that you can differentiate it from your release build.
5. Optionally, create "mirrored" directories in the `app/src/debug/res` directory for all of your app's icons that you want to change for your debug build. This part isn't technically critical, but it can make it easier to quickly spot your debug builds on a device if its icon is noticeable different.
And that's it! View [here](http://tools.android.com/tech-docs/new-build-system/resource-merging) for more details on how resource merging works in Android.
================================================
FILE: docs/multi-deployment-testing-ios.md
================================================
### iOS
> NOTE
>
> Complete demos configured with "multi-deployment testing" feature are [here]:
> * **without using cocoa pods**: [link](https://github.com/microsoft/react-native-code-push/files/1259957/rncp976.copy.zip)
> * **using cocoa pods**: [link](https://github.com/microsoft/react-native-code-push/files/1172217/rncp893.copy.zip)
Xcode allows you to define custom build settings for each "configuration" (like debug, release), which can then be referenced as the value of keys within the `Info.plist` file (like the `CodePushDeploymentKey` setting). This mechanism allows you to easily configure your builds to produce binaries, which are configured to synchronize with different CodePush deployments.
To set this up, perform the following steps:
1. Open up your Xcode project and select your project in the `Project navigator` window
2. Ensure the project node is selected, as opposed to one of your targets
3. Select the `Info` tab
4. Click the `+` button within the `Configurations` section and select `Duplicate "Release" Configuration`

5. Name the new configuration `Staging` (or whatever you prefer)
6. Select the `Build Settings` tab
7. Click the `+` button on the toolbar and select `Add User-Defined Setting`

Name this new setting something like `Multi_Deployment_Config`. Go to the setting and add value `$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)` for Release. After that add value `$(BUILD_DIR)/Release$(EFFECTIVE_PLATFORM_NAME)` for Staging

*NOTE: For Xcode 10 and lower version: Go to Build Location -> Per-configuration Build Products Path -> Staging and change Staging value from $(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) to $(BUILD_DIR)/Release$(EFFECTIVE_PLATFORM_NAME)*

*NOTE: Due to https://github.com/facebook/react-native/issues/11813, we have to do this step to make it possible to use other configurations than Debug or Release on RN 0.40.0 or higher.*
8. Click the `+` button again on the toolbar and select `Add User-Defined Setting`
Name this new setting something like `CODEPUSH_KEY`, expand it, and specify your `Staging` deployment key for the `Staging` config and your `Production` deployment key for the `Release` config.

*NOTE: As a reminder, you can retrieve these keys by running `appcenter codepush deployment list -a / -k` from your terminal.*
9. Open your project's `Info.plist` file and change the value of your `CodePushDeploymentKey` entry to `$(CODEPUSH_KEY)`

And that's it! Now when you run or build your app, your staging builds will automatically be configured to sync with your `Staging` deployment, and your release builds will be configured to sync with your `Production` deployment.
*NOTE: CocoaPods users may need to run `pod install` before building with their new release configuration.*
*Note: If you encounter the error message `ld: library not found for ...`, please consult [this issue](https://github.com/microsoft/react-native-code-push/issues/426) for a possible solution.*
Additionally, if you want to give them seperate names and/or icons, you can modify the `Product Bundle Identifier`, `Product Name` and `Asset Catalog App Icon Set Name` build settings, which will allow your staging builds to be distinguishable from release builds when installed on the same device.
================================================
FILE: docs/setup-android.md
================================================
## Android Setup
* [Plugin Installation and Configuration for React Native 0.60 version and above](#plugin-installation-and-configuration-for-react-native-060-version-and-above-android)
* [Plugin Installation for React Native lower than 0.60 (Android)](#plugin-installation-for-react-native-lower-than-060-android)
* [Plugin Installation (Android - RNPM)](#plugin-installation-android---rnpm)
* [Plugin Installation (Android - Manual)](#plugin-installation-android---manual)
* [Plugin Configuration for React Native lower than 0.60 (Android)](#plugin-configuration-for-react-native-lower-than-060-android)
* [For React Native v0.29 - v0.59](#for-react-native-v029---v059)
* [For newly created React Native application](#for-newly-created-react-native-application)
* [For existing native application](#for-existing-native-application)
* [For React Native v0.19 - v0.28](#for-react-native-v019---v028)
* [Background React Instances](#background-react-instances)
* [For React Native >= v0.29 (Background React Instances)](#for-react-native--v029-background-react-instances)
* [For React Native v0.19 - v0.28 (Background React Instances)](#for-react-native-v019---v028-background-react-instances)
* [WIX React Native Navigation applications (ver 1.x)](#wix-react-native-navigation-applications)
* [Code Signing setup](#code-signing-setup)
In order to integrate CodePush into your Android project, please perform the following steps:
### Plugin Installation and Configuration for React Native 0.60 version and above (Android)
1. In your `android/settings.gradle` file, make the following additions at the end of the file:
```gradle
...
include ':app', ':react-native-code-push'
project(':react-native-code-push').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-code-push/android/app')
```
2. In your `android/app/build.gradle` file, add the `codepush.gradle` file as an additional build task definition to the end of the file:
```gradle
...
apply from: "../../node_modules/react-native-code-push/android/codepush.gradle"
...
```
3. Update the `MainApplication` file to use CodePush via the following changes:
For React Native 0.73 and above: update the `MainApplication.kt`
```kotlin
...
// 1. Import the plugin class.
import com.microsoft.codepush.react.CodePush
class MainApplication : Application(), ReactApplication {
override val reactNativeHost: ReactNativeHost =
object : DefaultReactNativeHost(this) {
...
// 2. Override the getJSBundleFile method in order to let
// the CodePush runtime determine where to get the JS
// bundle location from on each app start
override fun getJSBundleFile(): String {
return CodePush.getJSBundleFile()
}
};
}
```
For React Native 0.72 and below: update the `MainApplication.java`
```java
...
// 1. Import the plugin class.
import com.microsoft.codepush.react.CodePush;
public class MainApplication extends Application implements ReactApplication {
private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
...
// 2. Override the getJSBundleFile method in order to let
// the CodePush runtime determine where to get the JS
// bundle location from on each app start
@Override
protected String getJSBundleFile() {
return CodePush.getJSBundleFile();
}
};
}
```
4. Add the Deployment key to `strings.xml`:
To let the CodePush runtime know which deployment it should query for updates, open your app's `strings.xml` file and add a new string named `CodePushDeploymentKey`, whose value is the key of the deployment you want to configure this app against (like the key for the `Staging` deployment for the `FooBar` app). You can retrieve this value by running `appcenter codepush deployment list -a / -k` in the CodePush CLI (the `-k` flag is necessary since keys aren't displayed by default) and copying the value of the `Key` column which corresponds to the deployment you want to use (see below). Note that using the deployment's name (like Staging) will not work. The "friendly name" is intended only for authenticated management usage from the CLI, and not for public consumption within your app.

In order to effectively make use of the `Staging` and `Production` deployments that were created along with your CodePush app, refer to the [multi-deployment testing](../README.md#multi-deployment-testing) docs below before actually moving your app's usage of CodePush into production.
Your `strings.xml` should looks like this:
```xml
AppName
DeploymentKey
```
*Note: If you need to dynamically use a different deployment, you can also override your deployment key in JS code using [Code-Push options](./api-js.md#CodePushOptions)*
### Plugin Installation for React Native lower than 0.60 (Android)
In order to accommodate as many developer preferences as possible, the CodePush plugin supports Android installation via two mechanisms:
1. [**RNPM**](#plugin-installation-android---rnpm) - [React Native Package Manager (RNPM)](https://github.com/rnpm/rnpm) is an awesome tool that provides the simplest installation experience possible for React Native plugins. If you're already using it, or you want to use it, then we recommend this approach.
2. [**"Manual"**](#plugin-installation-android---manual) - If you don't want to depend on any additional tools or are fine with a few extra installation steps (it's a one-time thing), then go with this approach.
*Note: Due to a code change from the React Native repository, if your installed React Native version ranges from 0.29 to 0.32, we recommend following the manual steps to set up correctly. *
#### Plugin Installation (Android - RNPM)
1. As of v0.27 of React Native, `rnpm link` has already been merged into the React Native CLI. Simply run:
```
react-native link react-native-code-push
```
If your app uses a version of React Native that is lower than v0.27, run the following:
```
rnpm link react-native-code-push
```
*Note: If you don't already have RNPM installed, you can do so by simply running `npm i -g rnpm` and then executing the above command.*
2. If you're using RNPM >=1.6.0, you will be prompted for the deployment key you'd like to use. If you don't already have it, you can retrieve this value by running `appcenter codepush deployment list -a / -k`, or you can choose to ignore it (by simply hitting ``) and add it in later. To get started, we would recommend just using your `Staging` deployment key, so that you can test out the CodePush end-to-end.
And that's it for installation using RNPM! Continue below to the [Plugin Configuration](#plugin-configuration-for-react-native-lower-than-060-android) section to complete the setup.
#### Plugin Installation (Android - Manual)
1. In your `android/settings.gradle` file, make the following additions:
```gradle
include ':app', ':react-native-code-push'
project(':react-native-code-push').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-code-push/android/app')
```
2. In your `android/app/build.gradle` file, add the `:react-native-code-push` project as a compile-time dependency:
```gradle
...
dependencies {
...
compile project(':react-native-code-push')
}
```
3. In your `android/app/build.gradle` file, add the `codepush.gradle` file as an additional build task definition underneath `react.gradle`:
```gradle
...
apply from: "../../node_modules/react-native/react.gradle"
apply from: "../../node_modules/react-native-code-push/android/codepush.gradle"
...
```
### Plugin Configuration for React Native lower than 0.60 (Android)
*NOTE: If you used RNPM or `react-native link` to automatically link the plugin, these steps have already been done for you so you may skip this section.*
After installing the plugin and syncing your Android Studio project with Gradle, you need to configure your app to consult CodePush for the location of your JS bundle, since it will "take control" of managing the current and all future versions. To do this:
#### For React Native v0.29 - v0.59
##### For newly created React Native application
If you are integrating Code Push into React Native application please do the following steps:
Update the `MainApplication.java` file to use CodePush via the following changes:
```java
...
// 1. Import the plugin class.
import com.microsoft.codepush.react.CodePush;
public class MainApplication extends Application implements ReactApplication {
private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
...
// 2. Override the getJSBundleFile method in order to let
// the CodePush runtime determine where to get the JS
// bundle location from on each app start
@Override
protected String getJSBundleFile() {
return CodePush.getJSBundleFile();
}
@Override
protected List getPackages() {
// 3. Instantiate an instance of the CodePush runtime and add it to the list of
// existing packages, specifying the right deployment key. If you don't already
// have it, you can run "appcenter codepush deployment list -a / -k" to retrieve your key.
return Arrays.asList(
new MainReactPackage(),
new CodePush("deployment-key-here", MainApplication.this, BuildConfig.DEBUG)
);
}
};
}
```
*NOTE: For React Native v0.49+ please be sure that `getJSMainModuleName` function in the `MainApplication.java` file determines correct URL to fetch JS bundle (used when dev support is enabled, see [this](https://github.com/facebook/react-native/blob/c7f37074ac89f7e568ca26a6bad3bdb02812c39f/ReactAndroid/src/main/java/com/facebook/react/ReactNativeHost.java#L124) for more details) e.g.*
```
@Override
protected String getJSMainModuleName() {
return "index";
}
```
##### For existing native application
If you are integrating React Native into existing native application please do the following steps:
Update `MyReactActivity.java` (it could be named differently in your app) file to use CodePush via the following changes:
```java
...
// 1. Import the plugin class.
import com.microsoft.codepush.react.CodePush;
public class MyReactActivity extends Activity {
private ReactRootView mReactRootView;
private ReactInstanceManager mReactInstanceManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
...
mReactInstanceManager = ReactInstanceManager.builder()
// ...
// Add CodePush package
.addPackage(new CodePush("deployment-key-here", getApplicationContext(), BuildConfig.DEBUG))
// Get the JS Bundle File via Code Push
.setJSBundleFile(CodePush.getJSBundleFile())
// ...
.build();
mReactRootView.startReactApplication(mReactInstanceManager, "MyReactNativeApp", null);
setContentView(mReactRootView);
}
...
}
```
#### For React Native v0.19 - v0.28
Update the `MainActivity.java` file to use CodePush via the following changes:
```java
...
// 1. Import the plugin class (if you used RNPM to install the plugin, this
// should already be done for you automatically so you can skip this step).
import com.microsoft.codepush.react.CodePush;
public class MainActivity extends ReactActivity {
// 2. Override the getJSBundleFile method in order to let
// the CodePush runtime determine where to get the JS
// bundle location from on each app start
@Override
protected String getJSBundleFile() {
return CodePush.getJSBundleFile();
}
@Override
protected List getPackages() {
// 3. Instantiate an instance of the CodePush runtime and add it to the list of
// existing packages, specifying the right deployment key. If you don't already
// have it, you can run "appcenter codepush deployment list -a / -k" to retrieve your key.
return Arrays.asList(
new MainReactPackage(),
new CodePush("deployment-key-here", this, BuildConfig.DEBUG)
);
}
...
}
```
#### Background React Instances
*This section is only necessary if you're explicitly launching a React Native instance without an `Activity` (for example, from within a native push notification receiver). For these situations, CodePush must be told how to find your React Native instance.*
In order to update/restart your React Native instance, CodePush must be configured with a `ReactInstanceHolder` before attempting to restart an instance in the background. This is done in your `Application` implementation.
##### For React Native >= v0.29 (Background React Instances)
Update the `MainApplication.java` file to use CodePush via the following changes:
```java
...
// 1. Declare your ReactNativeHost to extend ReactInstanceHolder. ReactInstanceHolder is a subset of ReactNativeHost, so no additional implementation is needed.
import com.microsoft.codepush.react.ReactInstanceHolder;
public class MyReactNativeHost extends ReactNativeHost implements ReactInstanceHolder {
// ... usual overrides
}
// 2. Provide your ReactNativeHost to CodePush.
public class MainApplication extends Application implements ReactApplication {
private final MyReactNativeHost mReactNativeHost = new MyReactNativeHost(this);
@Override
public void onCreate() {
CodePush.setReactInstanceHolder(mReactNativeHost);
super.onCreate();
}
}
```
##### For React Native v0.19 - v0.28 (Background React Instances)
Before v0.29, React Native did not provide a `ReactNativeHost` abstraction. If you're launching a background instance, you'll likely have built your own, which should now implement `ReactInstanceHolder`. Once that's done:
```java
// 1. Provide your ReactInstanceHolder to CodePush.
public class MainApplication extends Application {
@Override
public void onCreate() {
// ... initialize your instance holder
CodePush.setReactInstanceHolder(myInstanceHolder);
super.onCreate();
}
}
```
In order to effectively make use of the `Staging` and `Production` deployments that were created along with your CodePush app, refer to the [multi-deployment testing](../README.md#multi-deployment-testing) docs below before actually moving your app's usage of CodePush into production.
#### WIX React Native Navigation applications
If you are using [WIX React Native Navigation **version 1.x**](https://github.com/wix/react-native-navigation) based application, please do the following steps to integrate CodePush:
1. No need to change `MainActivity.java` file, so if you are integrating CodePush to newly created RNN application it might be looking like this:
```java
import com.facebook.react.ReactActivity;
import com.reactnativenavigation.controllers.SplashActivity;
public class MainActivity extends SplashActivity {
}
```
2. Update the `MainApplication.java` file to use CodePush via the following changes:
```java
// ...
import com.facebook.react.ReactInstanceManager;
// Add CodePush imports
import com.microsoft.codepush.react.ReactInstanceHolder;
import com.microsoft.codepush.react.CodePush;
public class MainApplication extends NavigationApplication implements ReactInstanceHolder {
@Override
public boolean isDebug() {
// Make sure you are using BuildConfig from your own application
return BuildConfig.DEBUG;
}
protected List getPackages() {
// Add additional packages you require here
return Arrays.asList(
new CodePush("deployment-key-here", getApplicationContext(), BuildConfig.DEBUG)
);
}
@Override
public List createAdditionalReactPackages() {
return getPackages();
}
@Override
public String getJSBundleFile() {
// Override default getJSBundleFile method with the one CodePush is providing
return CodePush.getJSBundleFile();
}
@Override
public String getJSMainModuleName() {
return "index";
}
@Override
public ReactInstanceManager getReactInstanceManager() {
// CodePush must be told how to find React Native instance
return getReactNativeHost().getReactInstanceManager();
}
}
```
If you are using [WIX React Native Navigation **version 2.x**](https://github.com/wix/react-native-navigation/tree/v2) based application, please do the following steps to integrate CodePush:
1. As per React Native Navigation's documentation, `MainActivity.java` should extend `NavigationActivity`, no changes required to incorporate CodePush:
```java
import com.reactnativenavigation.NavigationActivity;
public class MainActivity extends NavigationActivity {
}
```
2. Update the `MainApplication.java` file to use CodePush via the following changes:
```java
// ...
import com.facebook.react.ReactInstanceManager;
// Add CodePush imports
import com.microsoft.codepush.react.CodePush;
public class MainApplication extends NavigationApplication {
@Override
public boolean isDebug() {
return BuildConfig.DEBUG;
}
@Override
protected ReactGateway createReactGateway() {
ReactNativeHost host = new NavigationReactNativeHost(this, isDebug(), createAdditionalReactPackages()) {
@javax.annotation.Nullable
@Override
protected String getJSBundleFile() {
return CodePush.getJSBundleFile();
}
};
return new ReactGateway(this, isDebug(), host);
}
@Override
public List createAdditionalReactPackages() {
return Arrays.asList(
new CodePush("deployment-key-here", getApplicationContext(), isDebug())
//,MainReactPackage , etc...
}
}
```
### Code Signing setup
Starting with CLI version **2.1.0** you can self sign bundles during release and verify its signature before installation of update. For more info about Code Signing please refer to [relevant code-push documentation section](https://github.com/microsoft/code-push/tree/v3.0.1/cli#code-signing). In order to use Public Key for Code Signing you need to do following steps:
Add `CodePushPublicKey` string item to `/path_to_your_app/android/app/src/main/res/values/strings.xml`. It may looks like this:
```xml
my_app
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtPSR9lkGzZ4FR0lxF+ZA
P6jJ8+Xi5L601BPN4QESoRVSrJM08roOCVrs4qoYqYJy3Of2cQWvNBEh8ti3FhHu
tiuLFpNdfzM4DjAw0Ti5hOTfTixqVBXTJPYpSjDh7K6tUvp9MV0l5q/Ps3se1vud
M1/X6g54lIX/QoEXTdMgR+SKXvlUIC13T7GkDHT6Z4RlwxkWkOmf2tGguRcEBL6j
ww7w/3g0kWILz7nNPtXyDhIB9WLH7MKSJWdVCZm+cAqabUfpCFo7sHiyHLnUxcVY
OTw3sz9ceaci7z2r8SZdsfjyjiDJrq69eWtvKVUpredy9HtyALtNuLjDITahdh8A
zwIDAQAB
-----END PUBLIC KEY-----
```
#### For React Native <= v0.60 you should configure the `CodePush` instance to use this parameter using one of the following approaches
##### Using constructor
```java
new CodePush(
"deployment-key",
getApplicationContext(),
BuildConfig.DEBUG,
R.string.CodePushPublicKey)
```
##### Using builder
```java
new CodePushBuilder("deployment-key-here",getApplicationContext())
.setIsDebugMode(BuildConfig.DEBUG)
.setPublicKeyResourceDescriptor(R.string.CodePushPublicKey)
.build()
```
================================================
FILE: docs/setup-ios.md
================================================
## iOS Setup
Once you've acquired the CodePush plugin, you need to integrate it into the Xcode project of your React Native app and configure it correctly. To do this, take the following steps:
### Plugin Installation and Configuration for React Native 0.60 version and above (iOS)
1. Run `cd ios && pod install && cd ..` to install all the necessary CocoaPods dependencies.
2. Open up the `AppDelegate.m` file, and add an import statement for the CodePush headers:
```objective-c
#import
```
3. Find the following line of code, which sets the source URL for bridge for production releases:
```objective-c
return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
```
4. Replace it with this line:
```objective-c
return [CodePush bundleURL];
```
This change configures your app to always load the most recent version of your app's JS bundle. On the first launch, this will correspond to the file that was compiled with the app. However, after an update has been pushed via CodePush, this will return the location of the most recently installed update.
*NOTE: The `bundleURL` method assumes your app's JS bundle is named `main.jsbundle`. If you have configured your app to use a different file name, simply call the `bundleURLForResource:` method (which assumes you're using the `.jsbundle` extension) or `bundleURLForResource:withExtension:` method instead, in order to overwrite that default behavior*
Typically, you're only going to want to use CodePush to resolve your JS bundle location within release builds, and therefore, we recommend using the `DEBUG` pre-processor macro to dynamically switch between using the packager server and CodePush, depending on whether you are debugging or not. This will make it much simpler to ensure you get the right behavior you want in production, while still being able to use the Chrome Dev Tools, live reload, etc. at debug-time.
Your `sourceURLForBridge` method should look like this:
```objective-c
- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
{
#if DEBUG
return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"];
#else
return [CodePush bundleURL];
#endif
}
```
5. Add the Deployment key to `Info.plist`:
To let the CodePush runtime know which deployment it should query for updates against, open your app's `Info.plist` file and add a new entry named `CodePushDeploymentKey`, whose value is the key of the deployment you want to configure this app against (like the key for the `Staging` deployment for the `FooBar` app). You can retrieve this value by running `appcenter codepush deployment list -a / -k` in the AppCenter CLI (the `-k` flag is necessary since keys aren't displayed by default) and copying the value of the `Key` column which corresponds to the deployment you want to use (see below). Note that using the deployment's name (like Staging) will not work. That "friendly name" is intended only for authenticated management usage from the CLI, and not for public consumption within your app.

In order to effectively make use of the `Staging` and `Production` deployments that were created along with your CodePush app, refer to the [multi-deployment testing](../README.md#multi-deployment-testing) docs below before actually moving your app's usage of CodePush into production.
*Note: If you need to dynamically use a different deployment, you can also override your deployment key in JS code using [Code-Push options](./api-js.md#CodePushOptions)*
### Plugin Installation for React Native lower than 0.60 (iOS)
In order to accommodate as many developer preferences as possible, the CodePush plugin supports iOS installation via three mechanisms:
1. [**RNPM**](#plugin-installation-ios---rnpm) - [React Native Package Manager (RNPM)](https://github.com/rnpm/rnpm) is an awesome tool that provides the simplest installation experience possible for React Native plugins. If you're already using it, or you want to use it, then we recommend this approach.
2. [**CocoaPods**](#plugin-installation-ios---cocoapods) - If you're building a native iOS app that is embedding React Native into it, or you simply prefer using [CocoaPods](https://cocoapods.org), then we recommend using the Podspec file that we ship as part of our plugin.
3. [**"Manual"**](#plugin-installation-ios---manual) - If you don't want to depend on any additional tools or are fine with a few extra installation steps (it's a one-time thing), then go with this approach.
#### Plugin Installation (iOS - RNPM)
1. As of v0.27 of React Native, `rnpm link` has already been merged into the React Native CLI. Simply run:
```
react-native link react-native-code-push
```
If your app uses a version of React Native that is lower than v0.27, run the following:
```
rnpm link react-native-code-push
```
*Note: If you don't already have RNPM installed, you can do so by simply running `npm i -g rnpm` and then executing the above command. If you already have RNPM installed, make sure you have v1.9.0+ in order to benefit from this one step install.*
2. You will be prompted for the deployment key you'd like to use. If you don't already have it, you can retrieve this value by running `appcenter codepush deployment list -a / -k`, or you can choose to ignore it (by simply hitting ``) and add it in later. To get started, we would recommend just using your `Staging` deployment key, so that you can test out the CodePush end-to-end.
And that's it! Isn't RNPM awesome? :)
#### Plugin Installation (iOS - CocoaPods)
1. Add the ReactNative and CodePush plugin dependencies to your `Podfile`, pointing at the path where NPM installed it
```
# React Native requirements
pod 'React', :path => '../node_modules/react-native', :subspecs => [
'Core',
'CxxBridge', # Include this for RN >= 0.47
'DevSupport', # Include this to enable In-App Devmenu if RN >= 0.43
'RCTText',
'RCTNetwork',
'RCTWebSocket', # Needed for debugging
'RCTAnimation', # Needed for FlatList and animations running on native UI thread
# Add any other subspecs you want to use in your project
]
# Explicitly include Yoga if you are using RN >= 0.42.0
pod 'yoga', :path => '../node_modules/react-native/ReactCommon/yoga'
pod 'DoubleConversion', :podspec => '../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec'
pod 'glog', :podspec => '../node_modules/react-native/third-party-podspecs/glog.podspec'
pod 'Folly', :podspec => '../node_modules/react-native/third-party-podspecs/Folly.podspec'
# CodePush plugin dependency
pod 'CodePush', :path => '../node_modules/react-native-code-push'
```
*NOTE: The above path needs to be relative to your app's `Podfile`, so adjust it as necessary.*
*NOTE: `JWT` library should be >= version 3.0.x*
2. Run `pod install`
*NOTE: The CodePush `.podspec` depends on the `React` pod, and so in order to ensure that it can correctly use the version of React Native that your app is built with, please make sure to define the `React` dependency in your app's `Podfile` as explained [here](https://facebook.github.io/react-native/docs/integration-with-existing-apps.html#podfile).*
#### Plugin Installation (iOS - Manual)
1. Open your app's Xcode project
2. Find the `CodePush.xcodeproj` file within the `node_modules/react-native-code-push/ios` directory (or `node_modules/react-native-code-push` for <=`1.7.3-beta` installations) and drag it into the `Libraries` node in Xcode

3. Select the project node in Xcode and select the "Build Phases" tab of your project configuration.
4. Drag `libCodePush.a` from `Libraries/CodePush.xcodeproj/Products` into the "Link Binary With Libraries" section of your project's "Build Phases" configuration.

5. Click the plus sign underneath the "Link Binary With Libraries" list and select the `libz.tbd` library underneath the `iOS 9.1` node.

*Note: Alternatively, if you prefer, you can add the `-lz` flag to the `Other Linker Flags` field in the `Linking` section of the `Build Settings`.*
### Plugin Configuration for React Native lower than 0.60 (iOS)
*NOTE: If you used RNPM or `react-native link` to automatically link the plugin, these steps have already been done for you so you may skip this section.*
Once your Xcode project has been setup to build/link the CodePush plugin, you need to configure your app to consult CodePush for the location of your JS bundle, since it is responsible for synchronizing it with updates that are released to the CodePush server. To do this, perform the following steps:
1. Open up the `AppDelegate.m` file, and add an import statement for the CodePush headers:
```objective-c
#import
```
For React Native 0.59 - 0.59.10:
2. Find the following line of code, which sets the source URL for bridge for production releases:
```objective-c
return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
```
3. Replace it with this line:
```objective-c
return [CodePush bundleURL];
```
For React Native 0.58 and below:
2. Find the following line of code, which loads your JS Bundle from the app binary for production releases:
```objective-c
jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];
```
3. Replace it with this line:
```objective-c
jsCodeLocation = [CodePush bundleURL];
```
This change configures your app to always load the most recent version of your app's JS bundle. On the first launch, this will correspond to the file that was compiled with the app. However, after an update has been pushed via CodePush, this will return the location of the most recently installed update.
*NOTE: The `bundleURL` method assumes your app's JS bundle is named `main.jsbundle`. If you have configured your app to use a different file name, simply call the `bundleURLForResource:` method (which assumes you're using the `.jsbundle` extension) or `bundleURLForResource:withExtension:` method instead, in order to overwrite that default behavior*
Typically, you're only going to want to use CodePush to resolve your JS bundle location within release builds, and therefore, we recommend using the `DEBUG` pre-processor macro to dynamically switch between using the packager server and CodePush, depending on whether you are debugging or not. This will make it much simpler to ensure you get the right behavior you want in production, while still being able to use the Chrome Dev Tools, live reload, etc. at debug-time.
For React Native 0.59 - 0.59.10:
```objective-c
- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
{
#if DEBUG
return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];
#else
return [CodePush bundleURL];
#endif
}
```
For React Native 0.49 - 0.58:
```objective-c
NSURL *jsCodeLocation;
#ifdef DEBUG
jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];
#else
jsCodeLocation = [CodePush bundleURL];
#endif
```
For React Native 0.48 and below:
```objective-c
NSURL *jsCodeLocation;
#ifdef DEBUG
jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/index.ios.bundle?platform=ios&dev=true"];
#else
jsCodeLocation = [CodePush bundleURL];
#endif
```
To let the CodePush runtime know which deployment it should query for updates against, open your app's `Info.plist` file and add a new entry named `CodePushDeploymentKey`, whose value is the key of the deployment you want to configure this app against (like the key for the `Staging` deployment for the `FooBar` app). You can retrieve this value by running `appcenter codepush deployment list -a / -k` in the AppCenter CLI (the `-k` flag is necessary since keys aren't displayed by default) and copying the value of the `Key` column which corresponds to the deployment you want to use (see below). Note that using the deployment's name (like Staging) will not work. That "friendly name" is intended only for authenticated management usage from the CLI, and not for public consumption within your app.

In order to effectively make use of the `Staging` and `Production` deployments that were created along with your CodePush app, refer to the [multi-deployment testing](../README.md#multi-deployment-testing) docs below before actually moving your app's usage of CodePush into production.
### HTTP exception domains configuration (iOS)
CodePush plugin makes HTTPS requests to the following domains:
- codepush.appcenter.ms
- codepush.blob.core.windows.net
- codepushupdates.azureedge.net
If you want to change the default HTTP security configuration for any of these domains, you have to define the [`NSAppTransportSecurity` (ATS)][ats] configuration inside your __Info.plist__ file:
```xml
NSAppTransportSecurity
NSExceptionDomains
codepush.appcenter.ms
```
Before doing anything, please [read the docs][ats] first.
[ats]: https://developer.apple.com/library/content/documentation/General/Reference/InfoPlistKeyReference/Articles/CocoaKeys.html#//apple_ref/doc/uid/TP40009251-SW33
### Code Signing setup
Starting with CLI version **2.1.0** you can self sign bundles during release and verify its signature before installation of update. For more info about Code Signing please refer to [relevant code-push documentation section](https://github.com/microsoft/code-push/tree/v3.0.1/cli#code-signing).
In order to configure Public Key for bundle verification you need to add record in `Info.plist` with name `CodePushPublicKey` and string value of public key content. Example:
```xml
CodePushPublicKey
-----BEGIN PUBLIC KEY-----
MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANkWYydPuyOumR/sn2agNBVDnzyRpM16NAUpYPGxNgjSEp0etkDNgzzdzyvyl+OsAGBYF3jCxYOXozum+uV5hQECAwEAAQ==
-----END PUBLIC KEY-----
```
================================================
FILE: docs/setup-windows.md
================================================
## Windows Setup
Once you've acquired the CodePush plugin, you need to integrate it into the Visual Studio project of your React Native app and configure it correctly. To do this, take the following steps:
### Plugin Installation and Configuration for React Native Windows 0.63.6 version and above
#### Plugin Installation (Windows-npx)
Once the plugin has been downloaded, run `npx react-native autolink-windows` in your application's root directory to automatically add the CodePush c++ project to your application's windows solution file.
#### Plugin Configuration (Windows)
1. Replace the following files located at `windows/` with those in the CodePushDemoAppCpp example app in this repo found at `Examples/CodePushDemoAppCpp/windows/CodePushDemoAppCpp`:
1. app.h
2. app.cpp
3. app.xaml
2. In the above files, replace any occurance of `CodePushDemoAppCpp` with the name of your application
3. Enter your application's app version and deployment key to the `configMap` object at the top of your app's `OnLaunched` method in `App.cpp`:
```c++
...
void App::OnLaunched(activation::LaunchActivatedEventArgs const& e)
{
winrt::Microsoft::CodePush::ReactNative::CodePushConfig::SetHost(Host());
auto configMap{ winrt::single_threaded_map() };
configMap.Insert(L"appVersion", L"1.0.0");
configMap.Insert(L"deploymentKey", L"");
winrt::Microsoft::CodePush::ReactNative::CodePushConfig::Init(configMap);
...
}
...
```
#### Plugin Configuration (Windows) C#
1. add name space `Microsoft.CodePush` to `App.xaml.cs`
2. add app version and deployment key to `configMap` at the start of your app's `OnLaunched` method in `App.xaml.cs`.
```c#
using Microsoft.CodePush;
...
protected override void OnLaunched(LaunchActivatedEventArgs e)
{
Microsoft.CodePush.ReactNative.CodePushConfig.SetHost(Host);
IDictionary configMap = new Dictionary();
configMap.Add("appVersion", "1.0.0");
configMap.Add("deploymentKey", "deployment key");
Microsoft.CodePush.ReactNative.CodePushConfig.Init(configMap);
...
}
...
```
### Plugin Installation and Configuration for React Native Windows lower than 0.60
#### Plugin Installation (Windows)
1. Open the Visual Studio solution located at `windows-legacy\\.sln` within your app
2. Right-click the solution node in the `Solution Explorer` window and select the `Add -> Existing Project...` menu item

3. Browse to the `node_modules\react-native-code-push\windows` directory, select the `CodePush.csproj` file and click `OK`
4. Back in the `Solution Explorer`, right-click the project node that is named after your app, and select the `Add -> Reference...` menu item

5. Select the `Projects` tab on the left hand side, check the `CodePush` item and then click `OK`

#### Plugin Configuration (Windows)
After installing the plugin, you need to configure your app to consult CodePush for the location of your JS bundle, since it will "take control" of managing the current and all future versions. To do this, update the `MainReactNativeHost.cs` file to use CodePush via the following changes:
```c#
...
// 1. Import the CodePush namespace
using CodePush.ReactNative;
...
class MainReactNativeHost : ReactNativeHost
{
// 2. Declare a private instance variable for the CodePushModule instance.
private CodePushReactPackage codePushReactPackage;
// 3. Update the JavaScriptBundleFile property to initalize the CodePush runtime,
// specifying the right deployment key, then use it to return the bundle URL from
// CodePush instead of statically from the binary. If you don't already have your
// deployment key, you can run "appcenter codepush deployment list -a / -k" to retrieve it.
protected override string JavaScriptBundleFile
{
get
{
codePushReactPackage = new CodePushReactPackage("deployment-key-here", this);
return codePushReactPackage.GetJavaScriptBundleFile();
}
}
// 4. Add the codePushReactPackage instance to the list of existing packages.
protected override List Packages
{
get
{
return new List
{
new MainReactPackage(),
...
codePushReactPackage
};
}
}
...
}
```
================================================
FILE: ios/CodePush/Base64/Base64/MF_Base64Additions.h
================================================
//
// MF_Base64Additions.h
// Base64 -- RFC 4648 compatible implementation
// see http://www.ietf.org/rfc/rfc4648.txt for more details
//
// Designed to be compiled with Automatic Reference Counting
//
// Created by Dave Poirier on 2012-06-14.
// Public Domain
// Hosted at https://github.com/ekscrypto/Base64
//
#import
@interface NSString (Base64Addition)
+(NSString *)stringFromBase64String:(NSString *)base64String;
+(NSString *)stringFromBase64UrlEncodedString:(NSString *)base64UrlEncodedString;
-(NSString *)base64String;
-(NSString *)base64UrlEncodedString;
@end
@interface NSData (Base64Addition)
+(NSData *)dataWithBase64String:(NSString *)base64String;
+(NSData *)dataWithBase64UrlEncodedString:(NSString *)base64UrlEncodedString;
-(NSString *)base64String;
-(NSString *)base64UrlEncodedString;
@end
@interface MF_Base64Codec : NSObject
+(NSData *)dataFromBase64String:(NSString *)base64String;
+(NSString *)base64StringFromData:(NSData *)data;
+(NSString *)base64UrlEncodedStringFromBase64String:(NSString *)base64String;
+(NSString *)base64StringFromBase64UrlEncodedString:(NSString *)base64UrlEncodedString;
@end
================================================
FILE: ios/CodePush/Base64/Base64/MF_Base64Additions.m
================================================
//
// MF_Base64Additions.m
// Base64 -- RFC 4648 compatible implementation
// see http://www.ietf.org/rfc/rfc4648.txt for more details
//
// Designed to be compiled with Automatic Reference Counting
//
// Created by Dave Poirier on 2012-06-14.
// Public Domain
// Hosted at https://github.com/ekscrypto/Base64
//
#import "MF_Base64Additions.h"
@implementation MF_Base64Codec
+(NSString *)base64StringFromBase64UrlEncodedString:(NSString *)base64UrlEncodedString
{
NSString *s = base64UrlEncodedString;
s = [s stringByReplacingOccurrencesOfString:@"-" withString:@"+"];
s = [s stringByReplacingOccurrencesOfString:@"_" withString:@"/"];
switch (s.length % 4) {
case 2:
s = [s stringByAppendingString:@"=="];
break;
case 3:
s = [s stringByAppendingString:@"="];
break;
default:
break;
}
return s;
}
+(NSString *)base64UrlEncodedStringFromBase64String:(NSString *)base64String
{
NSString *s = base64String;
s = [s stringByReplacingOccurrencesOfString:@"=" withString:@""];
s = [s stringByReplacingOccurrencesOfString:@"+" withString:@"-"];
s = [s stringByReplacingOccurrencesOfString:@"/" withString:@"_"];
return s;
}
+(NSData *)dataFromBase64String:(NSString *)encoding
{
NSData *data = nil;
unsigned char *decodedBytes = NULL;
@try {
#define __ 255
static char decodingTable[256] = {
__,__,__,__, __,__,__,__, __,__,__,__, __,__,__,__, // 0x00 - 0x0F
__,__,__,__, __,__,__,__, __,__,__,__, __,__,__,__, // 0x10 - 0x1F
__,__,__,__, __,__,__,__, __,__,__,62, __,__,__,63, // 0x20 - 0x2F
52,53,54,55, 56,57,58,59, 60,61,__,__, __, 0,__,__, // 0x30 - 0x3F
__, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11,12,13,14, // 0x40 - 0x4F
15,16,17,18, 19,20,21,22, 23,24,25,__, __,__,__,__, // 0x50 - 0x5F
__,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40, // 0x60 - 0x6F
41,42,43,44, 45,46,47,48, 49,50,51,__, __,__,__,__, // 0x70 - 0x7F
__,__,__,__, __,__,__,__, __,__,__,__, __,__,__,__, // 0x80 - 0x8F
__,__,__,__, __,__,__,__, __,__,__,__, __,__,__,__, // 0x90 - 0x9F
__,__,__,__, __,__,__,__, __,__,__,__, __,__,__,__, // 0xA0 - 0xAF
__,__,__,__, __,__,__,__, __,__,__,__, __,__,__,__, // 0xB0 - 0xBF
__,__,__,__, __,__,__,__, __,__,__,__, __,__,__,__, // 0xC0 - 0xCF
__,__,__,__, __,__,__,__, __,__,__,__, __,__,__,__, // 0xD0 - 0xDF
__,__,__,__, __,__,__,__, __,__,__,__, __,__,__,__, // 0xE0 - 0xEF
__,__,__,__, __,__,__,__, __,__,__,__, __,__,__,__, // 0xF0 - 0xFF
};
encoding = [encoding stringByReplacingOccurrencesOfString:@"=" withString:@""];
NSData *encodedData = [encoding dataUsingEncoding:NSASCIIStringEncoding];
unsigned char *encodedBytes = (unsigned char *)[encodedData bytes];
NSUInteger encodedLength = [encodedData length];
if( encodedLength >= (NSUIntegerMax - 3) ) return nil; // NSUInteger overflow check
NSUInteger encodedBlocks = (encodedLength+3) >> 2;
NSUInteger expectedDataLength = encodedBlocks * 3;
unsigned char decodingBlock[4];
decodedBytes = malloc(expectedDataLength);
if( decodedBytes != NULL ) {
NSUInteger i = 0;
NSUInteger j = 0;
NSUInteger k = 0;
unsigned char c;
while( i < encodedLength ) {
c = decodingTable[encodedBytes[i]];
i++;
if( c != __ ) {
decodingBlock[j] = c;
j++;
if( j == 4 ) {
decodedBytes[k] = (decodingBlock[0] << 2) | (decodingBlock[1] >> 4);
decodedBytes[k+1] = (decodingBlock[1] << 4) | (decodingBlock[2] >> 2);
decodedBytes[k+2] = (decodingBlock[2] << 6) | (decodingBlock[3]);
j = 0;
k += 3;
}
}
}
// Process left over bytes, if any
if( j == 3 ) {
decodedBytes[k] = (decodingBlock[0] << 2) | (decodingBlock[1] >> 4);
decodedBytes[k+1] = (decodingBlock[1] << 4) | (decodingBlock[2] >> 2);
k += 2;
} else if( j == 2 ) {
decodedBytes[k] = (decodingBlock[0] << 2) | (decodingBlock[1] >> 4);
k += 1;
}
data = [[NSData alloc] initWithBytes:decodedBytes length:k];
}
}
@catch (NSException *exception) {
data = nil;
NSLog(@"WARNING: error occured while decoding base 32 string: %@", exception);
}
@finally {
if( decodedBytes != NULL ) {
free( decodedBytes );
}
}
return data;
}
+(NSString *)base64StringFromData:(NSData *)data
{
NSString *encoding = nil;
unsigned char *encodingBytes = NULL;
@try {
static char encodingTable[64] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
static NSUInteger paddingTable[] = {0,2,1};
// Table 1: The Base 64 Alphabet
//
// Value Encoding Value Encoding Value Encoding Value Encoding
// 0 A 17 R 34 i 51 z
// 1 B 18 S 35 j 52 0
// 2 C 19 T 36 k 53 1
// 3 D 20 U 37 l 54 2
// 4 E 21 V 38 m 55 3
// 5 F 22 W 39 n 56 4
// 6 G 23 X 40 o 57 5
// 7 H 24 Y 41 p 58 6
// 8 I 25 Z 42 q 59 7
// 9 J 26 a 43 r 60 8
// 10 K 27 b 44 s 61 9
// 11 L 28 c 45 t 62 +
// 12 M 29 d 46 u 63 /
// 13 N 30 e 47 v
// 14 O 31 f 48 w (pad) =
// 15 P 32 g 49 x
// 16 Q 33 h 50 y
NSUInteger dataLength = [data length];
NSUInteger encodedBlocks = dataLength / 3;
if( (encodedBlocks + 1) >= (NSUIntegerMax / 4) ) return nil; // NSUInteger overflow check
NSUInteger padding = paddingTable[dataLength % 3];
if( padding > 0 ) encodedBlocks++;
NSUInteger encodedLength = encodedBlocks * 4;
encodingBytes = malloc(encodedLength);
if( encodingBytes != NULL ) {
NSUInteger rawBytesToProcess = dataLength;
NSUInteger rawBaseIndex = 0;
NSUInteger encodingBaseIndex = 0;
unsigned char *rawBytes = (unsigned char *)[data bytes];
unsigned char rawByte1, rawByte2, rawByte3;
while( rawBytesToProcess >= 3 ) {
rawByte1 = rawBytes[rawBaseIndex];
rawByte2 = rawBytes[rawBaseIndex+1];
rawByte3 = rawBytes[rawBaseIndex+2];
encodingBytes[encodingBaseIndex] = encodingTable[((rawByte1 >> 2) & 0x3F)];
encodingBytes[encodingBaseIndex+1] = encodingTable[((rawByte1 << 4) & 0x30) | ((rawByte2 >> 4) & 0x0F) ];
encodingBytes[encodingBaseIndex+2] = encodingTable[((rawByte2 << 2) & 0x3C) | ((rawByte3 >> 6) & 0x03) ];
encodingBytes[encodingBaseIndex+3] = encodingTable[(rawByte3 & 0x3F)];
rawBaseIndex += 3;
encodingBaseIndex += 4;
rawBytesToProcess -= 3;
}
rawByte2 = 0;
switch (dataLength-rawBaseIndex) {
case 2:
rawByte2 = rawBytes[rawBaseIndex+1];
case 1:
rawByte1 = rawBytes[rawBaseIndex];
encodingBytes[encodingBaseIndex] = encodingTable[((rawByte1 >> 2) & 0x3F)];
encodingBytes[encodingBaseIndex+1] = encodingTable[((rawByte1 << 4) & 0x30) | ((rawByte2 >> 4) & 0x0F) ];
encodingBytes[encodingBaseIndex+2] = encodingTable[((rawByte2 << 2) & 0x3C) ];
// we can skip rawByte3 since we have a partial block it would always be 0
break;
}
// compute location from where to begin inserting padding, it may overwrite some bytes from the partial block encoding
// if their value was 0 (cases 1-2).
encodingBaseIndex = encodedLength - padding;
while( padding-- > 0 ) {
encodingBytes[encodingBaseIndex++] = '=';
}
encoding = [[NSString alloc] initWithBytes:encodingBytes length:encodedLength encoding:NSASCIIStringEncoding];
}
}
@catch (NSException *exception) {
encoding = nil;
NSLog(@"WARNING: error occured while tring to encode base 32 data: %@", exception);
}
@finally {
if( encodingBytes != NULL ) {
free( encodingBytes );
}
}
return encoding;
}
@end
@implementation NSString (Base64Addition)
-(NSString *)base64String
{
NSData *utf8encoding = [self dataUsingEncoding:NSUTF8StringEncoding];
return [MF_Base64Codec base64StringFromData:utf8encoding];
}
-(NSString *)base64UrlEncodedString
{
return [MF_Base64Codec base64UrlEncodedStringFromBase64String:[self base64String]];
}
+(NSString *)stringFromBase64String:(NSString *)base64String
{
NSData *utf8encoding = [MF_Base64Codec dataFromBase64String:base64String];
return [[NSString alloc] initWithData:utf8encoding encoding:NSUTF8StringEncoding];
}
+(NSString *)stringFromBase64UrlEncodedString:(NSString *)base64UrlEncodedString
{
return [self stringFromBase64String:[MF_Base64Codec base64StringFromBase64UrlEncodedString:base64UrlEncodedString]];
}
@end
@implementation NSData (Base64Addition)
+(NSData *)dataWithBase64String:(NSString *)base64String
{
return [MF_Base64Codec dataFromBase64String:base64String];
}
+(NSData *)dataWithBase64UrlEncodedString:(NSString *)base64UrlEncodedString
{
return [self dataWithBase64String:[MF_Base64Codec base64StringFromBase64UrlEncodedString:base64UrlEncodedString]];
}
-(NSString *)base64String
{
return [MF_Base64Codec base64StringFromData:self];
}
-(NSString *)base64UrlEncodedString
{
return [MF_Base64Codec base64UrlEncodedStringFromBase64String:[self base64String]];
}
@end
================================================
FILE: ios/CodePush/Base64/README.md
================================================
[](https://github.com/ekscrypto/Base64)
Base64 Additions for Objective-C on Mac OS X and iOS
=======
Usage
----
Open the Xcode project file, and drag MF_Base64Additions.m/.h into your project.
In files where you want to use Base64 encoding/decoding, simply include the header file and use one of the provided NSData or NSString additions.
Example use:
#import "MF_Base64Additions.h"
NSString *helloWorld = @"Hello World";
NSString *helloInBase64 = [helloWorld base64String];
NSString *helloDecoded = [NSString stringFromBase64String:helloInBase64];
Performance
----
* Encoding: Approximately 4 to 5 times faster than using the equivalent SecTransform.
* Encoding: 30% faster than https://github.com/l4u/NSData-Base64
* Decoding: 5% faster than using the equivalent SecTransform.
* Decoding: 5% faster than https://github.com/l4u/NSData-Base64
Requirements
-----
* Compile with Automatic Reference Counting
* Compatible with Mac OSX 10.6+ and iOS 4.0+
Implementation
----
* Implemented as per RFC 4648, see http://www.ietf.org/rfc/rfc4648.txt for more details.
Licensing
----
* Public Domain
================================================
FILE: ios/CodePush/CodePush.h
================================================
#if __has_include()
#import
#elif __has_include("RCTEventEmitter.h")
#import "RCTEventEmitter.h"
#else
#import "React/RCTEventEmitter.h" // Required when used as a Pod in a Swift project
#endif
#import
@interface CodePush : RCTEventEmitter
+ (NSURL *)binaryBundleURL;
/*
* This method is used to retrieve the URL for the most recent
* version of the JavaScript bundle. This could be either the
* bundle that was packaged with the app binary, or the bundle
* that was downloaded as part of a CodePush update. The value returned
* should be used to "bootstrap" the React Native bridge.
*
* This method assumes that your JS bundle is named "main.jsbundle"
* and therefore, if it isn't, you should use either the bundleURLForResource:
* or bundleURLForResource:withExtension: methods to override that behavior.
*/
+ (NSURL *)bundleURL;
+ (NSURL *)bundleURLForResource:(NSString *)resourceName;
+ (NSURL *)bundleURLForResource:(NSString *)resourceName
withExtension:(NSString *)resourceExtension;
+ (NSURL *)bundleURLForResource:(NSString *)resourceName
withExtension:(NSString *)resourceExtension
subdirectory:(NSString *)resourceSubdirectory;
+ (NSURL *)bundleURLForResource:(NSString *)resourceName
withExtension:(NSString *)resourceExtension
subdirectory:(NSString *)resourceSubdirectory
bundle:(NSBundle *)resourceBundle;
+ (NSString *)getApplicationSupportDirectory;
+ (NSString *)bundleAssetsPath;
/*
* This method allows the version of the app's binary interface
* to be specified, which would otherwise default to the
* binary version of the app.
*/
+ (void)overrideAppVersion:(NSString *)appVersion;
/*
* This method allows dynamically setting the app's
* deployment key, in addition to setting it via
* the Info.plist file's CodePushDeploymentKey setting.
*/
+ (void)setDeploymentKey:(NSString *)deploymentKey;
/*
* This method checks to see whether a specific package hash
* has previously failed installation.
*/
+ (BOOL)isFailedHash:(NSString*)packageHash;
/*
* This method is used to get information about the latest rollback.
* This information will be used to decide whether the application
* should ignore the update or not.
*/
+ (NSDictionary*)getRollbackInfo;
/*
* This method is used to save information about the latest rollback.
* This information will be used to decide whether the application
* should ignore the update or not.
*/
+ (void)setLatestRollbackInfo:(NSString*)packageHash;
/*
* This method is used to get the count of rollback for the package
* using the latest rollback information.
*/
+ (int)getRollbackCountForPackage:(NSString*) packageHash fromLatestRollbackInfo:(NSMutableDictionary*) latestRollbackInfo;
/*
* This method checks to see whether a specific package hash
* represents a downloaded and installed update, that hasn't
* been applied yet via an app restart.
*/
+ (BOOL)isPendingUpdate:(NSString*)packageHash;
// The below methods are only used during tests.
+ (BOOL)isUsingTestConfiguration;
+ (void)setUsingTestConfiguration:(BOOL)shouldUseTestConfiguration;
+ (void)clearUpdates;
@end
@interface CodePushConfig : NSObject
@property (copy) NSString *appVersion;
@property (readonly) NSString *buildVersion;
@property (readonly) NSDictionary *configuration;
@property (copy) NSString *deploymentKey;
@property (copy) NSString *serverURL;
@property (copy) NSString *publicKey;
+ (instancetype)current;
@end
@interface CodePushDownloadHandler : NSObject
@property (strong) NSOutputStream *outputFileStream;
@property long long expectedContentLength;
@property long long receivedContentLength;
@property dispatch_queue_t operationQueue;
@property (copy) void (^progressCallback)(long long, long long);
@property (copy) void (^doneCallback)(BOOL);
@property (copy) void (^failCallback)(NSError *err);
@property NSString *downloadUrl;
- (id)init:(NSString *)downloadFilePath
operationQueue:(dispatch_queue_t)operationQueue
progressCallback:(void (^)(long long, long long))progressCallback
doneCallback:(void (^)(BOOL))doneCallback
failCallback:(void (^)(NSError *err))failCallback;
- (void)download:(NSString*)url;
@end
@interface CodePushErrorUtils : NSObject
+ (NSError *)errorWithMessage:(NSString *)errorMessage;
+ (BOOL)isCodePushError:(NSError *)error;
@end
@interface CodePushPackage : NSObject
+ (void)downloadPackage:(NSDictionary *)updatePackage
expectedBundleFileName:(NSString *)expectedBundleFileName
publicKey:(NSString *)publicKey
operationQueue:(dispatch_queue_t)operationQueue
progressCallback:(void (^)(long long, long long))progressCallback
doneCallback:(void (^)())doneCallback
failCallback:(void (^)(NSError *err))failCallback;
+ (NSDictionary *)getCurrentPackage:(NSError **)error;
+ (NSDictionary *)getPreviousPackage:(NSError **)error;
+ (NSString *)getCurrentPackageFolderPath:(NSError **)error;
+ (NSString *)getCurrentPackageBundlePath:(NSError **)error;
+ (NSString *)getCurrentPackageHash:(NSError **)error;
+ (NSDictionary *)getPackage:(NSString *)packageHash
error:(NSError **)error;
+ (NSString *)getPackageFolderPath:(NSString *)packageHash;
+ (BOOL)installPackage:(NSDictionary *)updatePackage
removePendingUpdate:(BOOL)removePendingUpdate
error:(NSError **)error;
+ (void)rollbackPackage;
// The below methods are only used during tests.
+ (void)clearUpdates;
+ (void)downloadAndReplaceCurrentBundle:(NSString *)remoteBundleUrl;
@end
@interface CodePushTelemetryManager : NSObject
+ (NSDictionary *)getBinaryUpdateReport:(NSString *)appVersion;
+ (NSDictionary *)getRetryStatusReport;
+ (NSDictionary *)getRollbackReport:(NSDictionary *)lastFailedPackage;
+ (NSDictionary *)getUpdateReport:(NSDictionary *)currentPackage;
+ (void)recordStatusReported:(NSDictionary *)statusReport;
+ (void)saveStatusReportForRetry:(NSDictionary *)statusReport;
@end
@interface CodePushUpdateUtils : NSObject
+ (BOOL)copyEntriesInFolder:(NSString *)sourceFolder
destFolder:(NSString *)destFolder
error:(NSError **)error;
+ (NSString *)findMainBundleInFolder:(NSString *)folderPath
expectedFileName:(NSString *)expectedFileName
error:(NSError **)error;
+ (NSString *)assetsFolderName;
+ (NSString *)getHashForBinaryContents:(NSURL *)binaryBundleUrl
error:(NSError **)error;
+ (NSString *)manifestFolderPrefix;
+ (NSString *)modifiedDateStringOfFileAtURL:(NSURL *)fileURL;
+ (BOOL)isHashIgnoredFor:(NSString *) relativePath;
+ (BOOL)verifyFolderHash:(NSString *)finalUpdateFolder
expectedHash:(NSString *)expectedHash
error:(NSError **)error;
// remove BEGIN / END tags and line breaks from public key string
+ (NSString *)getKeyValueFromPublicKeyString:(NSString *)publicKeyString;
+ (NSString *)getSignatureFilePath:(NSString *)updateFolderPath;
+ (NSDictionary *) verifyAndDecodeJWT:(NSString *) jwt
withPublicKey:(NSString *)publicKey
error:(NSError **)error;
+ (BOOL)verifyUpdateSignatureFor:(NSString *)updateFolderPath
expectedHash:(NSString *)newUpdateHash
withPublicKey:(NSString *)publicKeyString
error:(NSError **)error;
@end
void CPLog(NSString *formatString, ...);
typedef NS_ENUM(NSInteger, CodePushInstallMode) {
CodePushInstallModeImmediate,
CodePushInstallModeOnNextRestart,
CodePushInstallModeOnNextResume,
CodePushInstallModeOnNextSuspend
};
typedef NS_ENUM(NSInteger, CodePushUpdateState) {
CodePushUpdateStateRunning,
CodePushUpdateStatePending,
CodePushUpdateStateLatest
};
================================================
FILE: ios/CodePush/CodePush.m
================================================
#if __has_include()
#import
#import
#import
#import
#import
#import
#import
#else // back compatibility for RN version < 0.40
#import "RCTAssert.h"
#import "RCTBridgeModule.h"
#import "RCTConvert.h"
#import "RCTEventDispatcher.h"
#import "RCTRootView.h"
#import "RCTUtils.h"
#endif
#import "CodePush.h"
@interface CodePush ()
@end
@implementation CodePush {
BOOL _hasResumeListener;
BOOL _isFirstRunAfterUpdate;
int _minimumBackgroundDuration;
NSDate *_lastResignedDate;
CodePushInstallMode _installMode;
NSTimer *_appSuspendTimer;
// Used to coordinate the dispatching of download progress events to JS.
long long _latestExpectedContentLength;
long long _latestReceivedConentLength;
BOOL _didUpdateProgress;
BOOL _allowed;
BOOL _restartInProgress;
NSMutableArray *_restartQueue;
}
RCT_EXPORT_MODULE()
#pragma mark - Private constants
// These constants represent emitted events
static NSString *const DownloadProgressEvent = @"CodePushDownloadProgress";
// These constants represent valid deployment statuses
static NSString *const DeploymentFailed = @"DeploymentFailed";
static NSString *const DeploymentSucceeded = @"DeploymentSucceeded";
// These keys represent the names we use to store data in NSUserDefaults
static NSString *const FailedUpdatesKey = @"CODE_PUSH_FAILED_UPDATES";
static NSString *const PendingUpdateKey = @"CODE_PUSH_PENDING_UPDATE";
// These keys are already "namespaced" by the PendingUpdateKey, so
// their values don't need to be obfuscated to prevent collision with app data
static NSString *const PendingUpdateHashKey = @"hash";
static NSString *const PendingUpdateIsLoadingKey = @"isLoading";
// These keys are used to inspect/augment the metadata
// that is associated with an update's package.
static NSString *const AppVersionKey = @"appVersion";
static NSString *const BinaryBundleDateKey = @"binaryDate";
static NSString *const PackageHashKey = @"packageHash";
static NSString *const PackageIsPendingKey = @"isPending";
#pragma mark - Static variables
static BOOL isRunningBinaryVersion = NO;
static BOOL needToReportRollback = NO;
static BOOL testConfigurationFlag = NO;
// These values are used to save the NS bundle, name, extension and subdirectory
// for the JS bundle in the binary.
static NSBundle *bundleResourceBundle = nil;
static NSString *bundleResourceExtension = @"jsbundle";
static NSString *bundleResourceName = @"main";
static NSString *bundleResourceSubdirectory = nil;
// These keys represent the names we use to store information about the latest rollback
static NSString *const LatestRollbackInfoKey = @"LATEST_ROLLBACK_INFO";
static NSString *const LatestRollbackPackageHashKey = @"packageHash";
static NSString *const LatestRollbackTimeKey = @"time";
static NSString *const LatestRollbackCountKey = @"count";
+ (void)initialize
{
[super initialize];
if (self == [CodePush class]) {
// Use the mainBundle by default.
bundleResourceBundle = [NSBundle mainBundle];
}
}
#pragma mark - Public Obj-C API
+ (NSURL *)binaryBundleURL
{
return [bundleResourceBundle URLForResource:bundleResourceName
withExtension:bundleResourceExtension
subdirectory:bundleResourceSubdirectory];
}
+ (NSString *)bundleAssetsPath
{
NSString *resourcePath = [bundleResourceBundle resourcePath];
if (bundleResourceSubdirectory) {
resourcePath = [resourcePath stringByAppendingPathComponent:bundleResourceSubdirectory];
}
return [resourcePath stringByAppendingPathComponent:[CodePushUpdateUtils assetsFolderName]];
}
+ (NSURL *)bundleURL
{
return [self bundleURLForResource:bundleResourceName
withExtension:bundleResourceExtension
subdirectory:bundleResourceSubdirectory
bundle:bundleResourceBundle];
}
+ (NSURL *)bundleURLForResource:(NSString *)resourceName
{
return [self bundleURLForResource:resourceName
withExtension:bundleResourceExtension
subdirectory:bundleResourceSubdirectory
bundle:bundleResourceBundle];
}
+ (NSURL *)bundleURLForResource:(NSString *)resourceName
withExtension:(NSString *)resourceExtension
{
return [self bundleURLForResource:resourceName
withExtension:resourceExtension
subdirectory:bundleResourceSubdirectory
bundle:bundleResourceBundle];
}
+ (NSURL *)bundleURLForResource:(NSString *)resourceName
withExtension:(NSString *)resourceExtension
subdirectory:(NSString *)resourceSubdirectory
{
return [self bundleURLForResource:resourceName
withExtension:resourceExtension
subdirectory:resourceSubdirectory
bundle:bundleResourceBundle];
}
+ (NSURL *)bundleURLForResource:(NSString *)resourceName
withExtension:(NSString *)resourceExtension
subdirectory:(NSString *)resourceSubdirectory
bundle:(NSBundle *)resourceBundle
{
bundleResourceName = resourceName;
bundleResourceExtension = resourceExtension;
bundleResourceSubdirectory = resourceSubdirectory;
bundleResourceBundle = resourceBundle;
[self ensureBinaryBundleExists];
NSString *logMessageFormat = @"Loading JS bundle from %@";
NSError *error;
NSString *packageFile = [CodePushPackage getCurrentPackageBundlePath:&error];
NSURL *binaryBundleURL = [self binaryBundleURL];
if (error || !packageFile) {
CPLog(logMessageFormat, binaryBundleURL);
isRunningBinaryVersion = YES;
return binaryBundleURL;
}
NSString *binaryAppVersion = [[CodePushConfig current] appVersion];
NSDictionary *currentPackageMetadata = [CodePushPackage getCurrentPackage:&error];
if (error || !currentPackageMetadata) {
CPLog(logMessageFormat, binaryBundleURL);
isRunningBinaryVersion = YES;
return binaryBundleURL;
}
NSString *packageDate = [currentPackageMetadata objectForKey:BinaryBundleDateKey];
NSString *packageAppVersion = [currentPackageMetadata objectForKey:AppVersionKey];
if ([[CodePushUpdateUtils modifiedDateStringOfFileAtURL:binaryBundleURL] isEqualToString:packageDate] && ([CodePush isUsingTestConfiguration] ||[binaryAppVersion isEqualToString:packageAppVersion])) {
// Return package file because it is newer than the app store binary's JS bundle
NSURL *packageUrl = [[NSURL alloc] initFileURLWithPath:packageFile];
CPLog(logMessageFormat, packageUrl);
isRunningBinaryVersion = NO;
return packageUrl;
} else {
BOOL isRelease = NO;
#ifndef DEBUG
isRelease = YES;
#endif
if (isRelease || ![binaryAppVersion isEqualToString:packageAppVersion]) {
[CodePush clearUpdates];
}
CPLog(logMessageFormat, binaryBundleURL);
isRunningBinaryVersion = YES;
return binaryBundleURL;
}
}
+ (NSString *)getApplicationSupportDirectory
{
NSString *applicationSupportDirectory = [NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES) objectAtIndex:0];
return applicationSupportDirectory;
}
+ (void)overrideAppVersion:(NSString *)appVersion
{
[CodePushConfig current].appVersion = appVersion;
}
+ (void)setDeploymentKey:(NSString *)deploymentKey
{
[CodePushConfig current].deploymentKey = deploymentKey;
}
/*
* WARNING: This cleans up all downloaded and pending updates.
*/
+ (void)clearUpdates
{
[CodePushPackage clearUpdates];
[self removePendingUpdate];
[self removeFailedUpdates];
}
#pragma mark - Test-only methods
/*
* This returns a boolean value indicating whether CodePush has
* been set to run under a test configuration.
*/
+ (BOOL)isUsingTestConfiguration
{
return testConfigurationFlag;
}
/*
* This is used to enable an environment in which tests can be run.
* Specifically, it flips a boolean flag that causes bundles to be
* saved to a test folder and enables the ability to modify
* installed bundles on the fly from JavaScript.
*/
+ (void)setUsingTestConfiguration:(BOOL)shouldUseTestConfiguration
{
testConfigurationFlag = shouldUseTestConfiguration;
}
#pragma mark - Private API methods
@synthesize methodQueue = _methodQueue;
@synthesize pauseCallback = _pauseCallback;
@synthesize paused = _paused;
- (void)setPaused:(BOOL)paused
{
if (_paused != paused) {
_paused = paused;
if (_pauseCallback) {
_pauseCallback();
}
}
}
/*
* This method is used to clear updates that are installed
* under a different app version and hence don't apply anymore,
* during a debug run configuration and when the bridge is
* running the JS bundle from the dev server.
*/
- (void)clearDebugUpdates
{
dispatch_async(dispatch_get_main_queue(), ^{
if ([super.bridge.bundleURL.scheme hasPrefix:@"http"]) {
NSError *error;
NSString *binaryAppVersion = [[CodePushConfig current] appVersion];
NSDictionary *currentPackageMetadata = [CodePushPackage getCurrentPackage:&error];
if (currentPackageMetadata) {
NSString *packageAppVersion = [currentPackageMetadata objectForKey:AppVersionKey];
if (![binaryAppVersion isEqualToString:packageAppVersion]) {
[CodePush clearUpdates];
}
}
}
});
}
/*
* This method is used by the React Native bridge to allow
* our plugin to expose constants to the JS-side. In our case
* we're simply exporting enum values so that the JS and Native
* sides of the plugin can be in sync.
*/
- (NSDictionary *)constantsToExport
{
// Export the values of the CodePushInstallMode and CodePushUpdateState
// enums so that the script-side can easily stay in sync
return @{
@"codePushInstallModeOnNextRestart":@(CodePushInstallModeOnNextRestart),
@"codePushInstallModeImmediate": @(CodePushInstallModeImmediate),
@"codePushInstallModeOnNextResume": @(CodePushInstallModeOnNextResume),
@"codePushInstallModeOnNextSuspend": @(CodePushInstallModeOnNextSuspend),
@"codePushUpdateStateRunning": @(CodePushUpdateStateRunning),
@"codePushUpdateStatePending": @(CodePushUpdateStatePending),
@"codePushUpdateStateLatest": @(CodePushUpdateStateLatest)
};
};
+ (BOOL)requiresMainQueueSetup
{
return YES;
}
- (void)dealloc
{
// Ensure the global resume handler is cleared, so that
// this object isn't kept alive unnecessarily
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)dispatchDownloadProgressEvent {
// Notify the script-side about the progress
[self sendEventWithName:DownloadProgressEvent
body:@{
@"totalBytes" : [NSNumber
numberWithLongLong:_latestExpectedContentLength],
@"receivedBytes" : [NSNumber
numberWithLongLong:_latestReceivedConentLength]
}];
}
/*
* This method ensures that the app was packaged with a JS bundle
* file, and if not, it throws the appropriate exception.
*/
+ (void)ensureBinaryBundleExists
{
if (![self binaryBundleURL]) {
NSString *errorMessage;
#ifdef DEBUG
#if TARGET_IPHONE_SIMULATOR
errorMessage = @"React Native doesn't generate your app's JS bundle by default when deploying to the simulator. "
"If you'd like to test CodePush using the simulator, you can do one of the following depending on your "
"React Native version and/or preferred workflow:\n\n"
"1. Update your AppDelegate.m file to load the JS bundle from the packager instead of from CodePush. "
"You can still test your CodePush update experience using this workflow (Debug builds only).\n\n"
"2. Force the JS bundle to be generated in simulator builds by adding 'export FORCE_BUNDLING=true' to the script under "
"\"Build Phases\" > \"Bundle React Native code and images\" (React Native >=0.48 only).\n\n"
"3. Force the JS bundle to be generated in simulator builds by removing the if block that echoes "
"\"Skipping bundling for Simulator platform\" in the \"node_modules/react-native/packager/react-native-xcode.sh\" file (React Native <=0.47 only)\n\n"
"4. Deploy a Release build to the simulator, which unlike Debug builds, will generate the JS bundle (React Native >=0.22.0 only).";
#else
errorMessage = [NSString stringWithFormat:@"The specified JS bundle file wasn't found within the app's binary. Is \"%@\" the correct file name?", [bundleResourceName stringByAppendingPathExtension:bundleResourceExtension]];
#endif
#else
errorMessage = @"Something went wrong. Please verify if generated JS bundle is correct. ";
#endif
RCTFatal([CodePushErrorUtils errorWithMessage:errorMessage]);
}
}
- (instancetype)init
{
_allowed = YES;
_restartInProgress = NO;
_restartQueue = [NSMutableArray arrayWithCapacity:1];
self = [super init];
if (self) {
[self initializeUpdateAfterRestart];
}
return self;
}
/*
* This method is used when the app is started to either
* initialize a pending update or rollback a faulty update
* to the previous version.
*/
- (void)initializeUpdateAfterRestart
{
#ifdef DEBUG
[self clearDebugUpdates];
#endif
self.paused = YES;
NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults];
NSDictionary *pendingUpdate = [preferences objectForKey:PendingUpdateKey];
if (pendingUpdate) {
_isFirstRunAfterUpdate = YES;
BOOL updateIsLoading = [pendingUpdate[PendingUpdateIsLoadingKey] boolValue];
if (updateIsLoading) {
// Pending update was initialized, but notifyApplicationReady was not called.
// Therefore, deduce that it is a broken update and rollback.
CPLog(@"Update did not finish loading the last time, rolling back to a previous version.");
needToReportRollback = YES;
[self rollbackPackage];
} else {
// Mark that we tried to initialize the new update, so that if it crashes,
// we will know that we need to rollback when the app next starts.
[self savePendingUpdate:pendingUpdate[PendingUpdateHashKey]
isLoading:YES];
}
}
}
/*
* This method is used to get information about the latest rollback.
* This information will be used to decide whether the application
* should ignore the update or not.
*/
+ (NSDictionary *)getLatestRollbackInfo
{
NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults];
NSDictionary *latestRollbackInfo = [preferences objectForKey:LatestRollbackInfoKey];
return latestRollbackInfo;
}
/*
* This method is used to save information about the latest rollback.
* This information will be used to decide whether the application
* should ignore the update or not.
*/
+ (void)setLatestRollbackInfo:(NSString*)packageHash
{
if (packageHash == nil) {
return;
}
NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults];
NSMutableDictionary *latestRollbackInfo = [preferences objectForKey:LatestRollbackInfoKey];
if (latestRollbackInfo == nil) {
latestRollbackInfo = [[NSMutableDictionary alloc] init];
} else {
latestRollbackInfo = [latestRollbackInfo mutableCopy];
}
int initialRollbackCount = [self getRollbackCountForPackage: packageHash fromLatestRollbackInfo: latestRollbackInfo];
NSNumber *count = [NSNumber numberWithInt: initialRollbackCount + 1];
NSNumber *currentTimeMillis = [NSNumber numberWithDouble: [[NSDate date] timeIntervalSince1970] * 1000];
[latestRollbackInfo setValue:count forKey:LatestRollbackCountKey];
[latestRollbackInfo setValue:currentTimeMillis forKey:LatestRollbackTimeKey];
[latestRollbackInfo setValue:packageHash forKey:LatestRollbackPackageHashKey];
[preferences setObject:latestRollbackInfo forKey:LatestRollbackInfoKey];
[preferences synchronize];
}
/*
* This method is used to get the count of rollback for the package
* using the latest rollback information.
*/
+ (int)getRollbackCountForPackage:(NSString*) packageHash fromLatestRollbackInfo:(NSMutableDictionary*) latestRollbackInfo
{
NSString *oldPackageHash = [latestRollbackInfo objectForKey:LatestRollbackPackageHashKey];
if ([packageHash isEqualToString: oldPackageHash]) {
NSNumber *oldCount = [latestRollbackInfo objectForKey:LatestRollbackCountKey];
return [oldCount intValue];
} else {
return 0;
}
}
/*
* This method checks to see whether a specific package hash
* has previously failed installation.
*/
+ (BOOL)isFailedHash:(NSString*)packageHash
{
NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults];
NSMutableArray *failedUpdates = [preferences objectForKey:FailedUpdatesKey];
if (failedUpdates == nil || packageHash == nil) {
return NO;
} else {
for (NSDictionary *failedPackage in failedUpdates)
{
// Type check is needed for backwards compatibility, where we used to just store
// the failed package hash instead of the metadata. This only impacts "dev"
// scenarios, since in production we clear out old information whenever a new
// binary is applied.
if ([failedPackage isKindOfClass:[NSDictionary class]]) {
NSString *failedPackageHash = [failedPackage objectForKey:PackageHashKey];
if ([packageHash isEqualToString:failedPackageHash]) {
return YES;
}
}
}
return NO;
}
}
/*
* This method checks to see whether a specific package hash
* represents a downloaded and installed update, that hasn't
* been applied yet via an app restart.
*/
+ (BOOL)isPendingUpdate:(NSString*)packageHash
{
NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults];
NSDictionary *pendingUpdate = [preferences objectForKey:PendingUpdateKey];
// If there is a pending update whose "state" isn't loading, then we consider it "pending".
// Additionally, if a specific hash was provided, we ensure it matches that of the pending update.
BOOL updateIsPending = pendingUpdate &&
[pendingUpdate[PendingUpdateIsLoadingKey] boolValue] == NO &&
(!packageHash || [pendingUpdate[PendingUpdateHashKey] isEqualToString:packageHash]);
return updateIsPending;
}
/*
* This method updates the React Native bridge's bundle URL
* to point at the latest CodePush update, and then restarts
* the bridge. This isn't meant to be called directly.
*/
- (void)loadBundle
{
// This needs to be async dispatched because the bridge is not set on init
// when the app first starts, therefore rollbacks will not take effect.
dispatch_async(dispatch_get_main_queue(), ^{
// If the current bundle URL is using http(s), then assume the dev
// is debugging and therefore, shouldn't be redirected to a local
// file (since Chrome wouldn't support it). Otherwise, update
// the current bundle URL to point at the latest update
if ([CodePush isUsingTestConfiguration] || ![super.bridge.bundleURL.scheme hasPrefix:@"http"]) {
[super.bridge setValue:[CodePush bundleURL] forKey:@"bundleURL"];
}
RCTTriggerReloadCommandListeners(@"react-native-code-push: Restart");
});
}
/*
* This method is used when an update has failed installation
* and the app needs to be rolled back to the previous bundle.
* This method is automatically called when the rollback timer
* expires without the app indicating whether the update succeeded,
* and therefore, it shouldn't be called directly.
*/
- (void)rollbackPackage
{
NSError *error;
NSDictionary *failedPackage = [CodePushPackage getCurrentPackage:&error];
if (!failedPackage) {
if (error) {
CPLog(@"Error getting current update metadata during rollback: %@", error);
} else {
CPLog(@"Attempted to perform a rollback when there is no current update");
}
} else {
// Write the current package's metadata to the "failed list"
[self saveFailedUpdate:failedPackage];
}
// Rollback to the previous version and de-register the new update
[CodePushPackage rollbackPackage];
[CodePush removePendingUpdate];
[self loadBundle];
}
/*
* When an update failed to apply, this method can be called
* to store its hash so that it can be ignored on future
* attempts to check the server for an update.
*/
- (void)saveFailedUpdate:(NSDictionary *)failedPackage
{
if ([[self class] isFailedHash:[failedPackage objectForKey:PackageHashKey]]) {
return;
}
NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults];
NSMutableArray *failedUpdates = [preferences objectForKey:FailedUpdatesKey];
if (failedUpdates == nil) {
failedUpdates = [[NSMutableArray alloc] init];
} else {
// The NSUserDefaults sytem always returns immutable
// objects, regardless if you stored something mutable.
failedUpdates = [failedUpdates mutableCopy];
}
[failedUpdates addObject:failedPackage];
[preferences setObject:failedUpdates forKey:FailedUpdatesKey];
[preferences synchronize];
}
/*
* This method is used to clear away failed updates in the event that
* a new app store binary is installed.
*/
+ (void)removeFailedUpdates
{
NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults];
[preferences removeObjectForKey:FailedUpdatesKey];
[preferences synchronize];
}
/*
* This method is used to register the fact that a pending
* update succeeded and therefore can be removed.
*/
+ (void)removePendingUpdate
{
NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults];
[preferences removeObjectForKey:PendingUpdateKey];
[preferences synchronize];
}
/*
* When an update is installed whose mode isn't IMMEDIATE, this method
* can be called to store the pending update's metadata (e.g. packageHash)
* so that it can be used when the actual update application occurs at a later point.
*/
- (void)savePendingUpdate:(NSString *)packageHash
isLoading:(BOOL)isLoading
{
// Since we're not restarting, we need to store the fact that the update
// was installed, but hasn't yet become "active".
NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults];
NSDictionary *pendingUpdate = [[NSDictionary alloc] initWithObjectsAndKeys:
packageHash,PendingUpdateHashKey,
[NSNumber numberWithBool:isLoading],PendingUpdateIsLoadingKey, nil];
[preferences setObject:pendingUpdate forKey:PendingUpdateKey];
[preferences synchronize];
}
- (NSArray *)supportedEvents {
return @[DownloadProgressEvent];
}
// Determine how long the app was in the background
- (int)getDurationInBackground
{
int duration = 0;
if (_lastResignedDate) {
duration = [[NSDate date] timeIntervalSinceDate:_lastResignedDate];
}
return duration;
}
#pragma mark - Application lifecycle event handlers
// These three handlers will only be registered when there is
// a resume-based update still pending installation.
- (void)applicationDidBecomeActive
{
if (_installMode == CodePushInstallModeOnNextSuspend) {
int durationInBackground = [self getDurationInBackground];
// We shouldn't use loadBundle in this case, because _appSuspendTimer will call loadBundleOnTick.
// We should cancel timer for _appSuspendTimer because otherwise, we would call loadBundle two times.
if (durationInBackground < _minimumBackgroundDuration) {
[_appSuspendTimer invalidate];
_appSuspendTimer = nil;
}
}
}
- (void)applicationWillEnterForeground
{
if (_installMode == CodePushInstallModeOnNextResume) {
int durationInBackground = [self getDurationInBackground];
if (durationInBackground >= _minimumBackgroundDuration) {
[self restartAppInternal:NO];
}
}
}
- (void)applicationWillResignActive
{
// Save the current time so that when the app is later
// resumed, we can detect how long it was in the background.
_lastResignedDate = [NSDate date];
if (_installMode == CodePushInstallModeOnNextSuspend && [[self class] isPendingUpdate:nil]) {
_appSuspendTimer = [NSTimer scheduledTimerWithTimeInterval:_minimumBackgroundDuration
target:self
selector:@selector(loadBundleOnTick:)
userInfo:nil
repeats:NO];
}
}
-(void)loadBundleOnTick:(NSTimer *)timer {
[self restartAppInternal:NO];
}
#pragma mark - JavaScript-exported module methods (Public)
/*
* This is native-side of the RemotePackage.download method
*/
RCT_EXPORT_METHOD(downloadUpdate:(NSDictionary*)updatePackage
notifyProgress:(BOOL)notifyProgress
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
{
NSDictionary *mutableUpdatePackage = [updatePackage mutableCopy];
NSURL *binaryBundleURL = [CodePush binaryBundleURL];
if (binaryBundleURL != nil) {
[mutableUpdatePackage setValue:[CodePushUpdateUtils modifiedDateStringOfFileAtURL:binaryBundleURL]
forKey:BinaryBundleDateKey];
}
if (notifyProgress) {
// Set up and unpause the frame observer so that it can emit
// progress events every frame if the progress is updated.
_didUpdateProgress = NO;
self.paused = NO;
}
NSString * publicKey = [[CodePushConfig current] publicKey];
[CodePushPackage
downloadPackage:mutableUpdatePackage
expectedBundleFileName:[bundleResourceName stringByAppendingPathExtension:bundleResourceExtension]
publicKey:publicKey
operationQueue:_methodQueue
// The download is progressing forward
progressCallback:^(long long expectedContentLength, long long receivedContentLength) {
// Update the download progress so that the frame observer can notify the JS side
_latestExpectedContentLength = expectedContentLength;
_latestReceivedConentLength = receivedContentLength;
_didUpdateProgress = YES;
// If the download is completed, stop observing frame
// updates and synchronously send the last event.
if (expectedContentLength == receivedContentLength) {
_didUpdateProgress = NO;
self.paused = YES;
[self dispatchDownloadProgressEvent];
}
}
// The download completed
doneCallback:^{
NSError *err;
NSDictionary *newPackage = [CodePushPackage getPackage:mutableUpdatePackage[PackageHashKey] error:&err];
if (err) {
return reject([NSString stringWithFormat: @"%lu", (long)err.code], err.localizedDescription, err);
}
resolve(newPackage);
}
// The download failed
failCallback:^(NSError *err) {
if ([CodePushErrorUtils isCodePushError:err]) {
[self saveFailedUpdate:mutableUpdatePackage];
}
// Stop observing frame updates if the download fails.
_didUpdateProgress = NO;
self.paused = YES;
reject([NSString stringWithFormat: @"%lu", (long)err.code], err.localizedDescription, err);
}];
}
- (void)restartAppInternal:(BOOL)onlyIfUpdateIsPending
{
if (_restartInProgress) {
CPLog(@"Restart request queued until the current restart is completed.");
[_restartQueue addObject:@(onlyIfUpdateIsPending)];
return;
} else if (!_allowed) {
CPLog(@"Restart request queued until restarts are re-allowed.");
[_restartQueue addObject:@(onlyIfUpdateIsPending)];
return;
}
_restartInProgress = YES;
if (!onlyIfUpdateIsPending || [[self class] isPendingUpdate:nil]) {
[self loadBundle];
CPLog(@"Restarting app.");
return;
}
_restartInProgress = NO;
if ([_restartQueue count] > 0) {
BOOL buf = [_restartQueue valueForKey: @"@firstObject"];
[_restartQueue removeObjectAtIndex:0];
[self restartAppInternal:buf];
}
}
/*
* This is the native side of the CodePush.getConfiguration method. It isn't
* currently exposed via the "react-native-code-push" module, and is used
* internally only by the CodePush.checkForUpdate method in order to get the
* app version, as well as the deployment key that was configured in the Info.plist file.
*/
RCT_EXPORT_METHOD(getConfiguration:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
{
NSDictionary *configuration = [[CodePushConfig current] configuration];
NSError *error;
if (isRunningBinaryVersion) {
// isRunningBinaryVersion will not get set to "YES" if running against the packager.
NSString *binaryHash = [CodePushUpdateUtils getHashForBinaryContents:[CodePush binaryBundleURL] error:&error];
if (error) {
CPLog(@"Error obtaining hash for binary contents: %@", error);
resolve(configuration);
return;
}
if (binaryHash == nil) {
// The hash was not generated either due to a previous unknown error or the fact that
// the React Native assets were not bundled in the binary (e.g. during dev/simulator)
// builds.
resolve(configuration);
return;
}
NSMutableDictionary *mutableConfiguration = [configuration mutableCopy];
[mutableConfiguration setObject:binaryHash forKey:PackageHashKey];
resolve(mutableConfiguration);
return;
}
resolve(configuration);
}
/*
* This method is the native side of the CodePush.getUpdateMetadata method.
*/
RCT_EXPORT_METHOD(getUpdateMetadata:(CodePushUpdateState)updateState
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
{
NSError *error;
NSMutableDictionary *package = [[CodePushPackage getCurrentPackage:&error] mutableCopy];
if (error) {
return reject([NSString stringWithFormat: @"%lu", (long)error.code], error.localizedDescription, error);
} else if (package == nil) {
// The app hasn't downloaded any CodePush updates yet,
// so we simply return nil regardless if the user
// wanted to retrieve the pending or running update.
return resolve(nil);
}
// We have a CodePush update, so let's see if it's currently in a pending state.
BOOL currentUpdateIsPending = [[self class] isPendingUpdate:[package objectForKey:PackageHashKey]];
if (updateState == CodePushUpdateStatePending && !currentUpdateIsPending) {
// The caller wanted a pending update
// but there isn't currently one.
resolve(nil);
} else if (updateState == CodePushUpdateStateRunning && currentUpdateIsPending) {
// The caller wants the running update, but the current
// one is pending, so we need to grab the previous.
resolve([CodePushPackage getPreviousPackage:&error]);
} else {
// The current package satisfies the request:
// 1) Caller wanted a pending, and there is a pending update
// 2) Caller wanted the running update, and there isn't a pending
// 3) Caller wants the latest update, regardless if it's pending or not
if (isRunningBinaryVersion) {
// This only matters in Debug builds. Since we do not clear "outdated" updates,
// we need to indicate to the JS side that somehow we have a current update on
// disk that is not actually running.
[package setObject:@(YES) forKey:@"_isDebugOnly"];
}
// Enable differentiating pending vs. non-pending updates
[package setObject:@(currentUpdateIsPending) forKey:PackageIsPendingKey];
resolve(package);
}
}
/*
* This method is the native side of the LocalPackage.install method.
*/
RCT_EXPORT_METHOD(installUpdate:(NSDictionary*)updatePackage
installMode:(CodePushInstallMode)installMode
minimumBackgroundDuration:(int)minimumBackgroundDuration
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
{
NSError *error;
[CodePushPackage installPackage:updatePackage
removePendingUpdate:[[self class] isPendingUpdate:nil]
error:&error];
if (error) {
reject([NSString stringWithFormat: @"%lu", (long)error.code], error.localizedDescription, error);
} else {
[self savePendingUpdate:updatePackage[PackageHashKey]
isLoading:NO];
_installMode = installMode;
if (_installMode == CodePushInstallModeOnNextResume || _installMode == CodePushInstallModeOnNextSuspend) {
_minimumBackgroundDuration = minimumBackgroundDuration;
if (!_hasResumeListener) {
// Ensure we do not add the listener twice.
// Register for app resume notifications so that we
// can check for pending updates which support "restart on resume"
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(applicationDidBecomeActive)
name:UIApplicationDidBecomeActiveNotification
object:RCTSharedApplication()];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(applicationWillEnterForeground)
name:UIApplicationWillEnterForegroundNotification
object:RCTSharedApplication()];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(applicationWillResignActive)
name:UIApplicationWillResignActiveNotification
object:RCTSharedApplication()];
_hasResumeListener = YES;
}
}
// Signal to JS that the update has been applied.
resolve(nil);
}
}
/*
* This method isn't publicly exposed via the "react-native-code-push"
* module, and is only used internally to populate the RemotePackage.failedInstall property.
*/
RCT_EXPORT_METHOD(isFailedUpdate:(NSString *)packageHash
resolve:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject)
{
BOOL isFailedHash = [[self class] isFailedHash:packageHash];
resolve(@(isFailedHash));
}
RCT_EXPORT_METHOD(setLatestRollbackInfo:(NSString *)packageHash
resolve:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject)
{
[[self class] setLatestRollbackInfo:packageHash];
resolve(nil);
}
RCT_EXPORT_METHOD(getLatestRollbackInfo:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
{
NSDictionary *latestRollbackInfo = [[self class] getLatestRollbackInfo];
resolve(latestRollbackInfo);
}
/*
* This method isn't publicly exposed via the "react-native-code-push"
* module, and is only used internally to populate the LocalPackage.isFirstRun property.
*/
RCT_EXPORT_METHOD(isFirstRun:(NSString *)packageHash
resolve:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
{
NSError *error;
BOOL isFirstRun = _isFirstRunAfterUpdate
&& nil != packageHash
&& [packageHash length] > 0
&& [packageHash isEqualToString:[CodePushPackage getCurrentPackageHash:&error]];
resolve(@(isFirstRun));
}
/*
* This method is the native side of the CodePush.notifyApplicationReady() method.
*/
RCT_EXPORT_METHOD(notifyApplicationReady:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
{
[CodePush removePendingUpdate];
resolve(nil);
}
RCT_EXPORT_METHOD(allow:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
{
CPLog(@"Re-allowing restarts.");
_allowed = YES;
if ([_restartQueue count] > 0) {
CPLog(@"Executing pending restart.");
BOOL buf = [_restartQueue valueForKey: @"@firstObject"];
[_restartQueue removeObjectAtIndex:0];
[self restartAppInternal:buf];
}
resolve(nil);
}
RCT_EXPORT_METHOD(clearPendingRestart:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
{
[_restartQueue removeAllObjects];
resolve(nil);
}
RCT_EXPORT_METHOD(disallow:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
{
CPLog(@"Disallowing restarts.");
_allowed = NO;
resolve(nil);
}
/*
* This method is the native side of the CodePush.restartApp() method.
*/
RCT_EXPORT_METHOD(restartApp:(BOOL)onlyIfUpdateIsPending
resolve:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
{
[self restartAppInternal:onlyIfUpdateIsPending];
resolve(nil);
}
/*
* This method clears CodePush's downloaded updates.
* It is needed to switch to a different deployment if the current deployment is more recent.
* Note: we don’t recommend to use this method in scenarios other than that (CodePush will call this method
* automatically when needed in other cases) as it could lead to unpredictable behavior.
*/
RCT_EXPORT_METHOD(clearUpdates) {
CPLog(@"Clearing updates.");
[CodePush clearUpdates];
}
#pragma mark - JavaScript-exported module methods (Private)
/*
* This method is the native side of the CodePush.downloadAndReplaceCurrentBundle()
* method, which replaces the current bundle with the one downloaded from
* removeBundleUrl. It is only to be used during tests and no-ops if the test
* configuration flag is not set.
*/
RCT_EXPORT_METHOD(downloadAndReplaceCurrentBundle:(NSString *)remoteBundleUrl)
{
if ([CodePush isUsingTestConfiguration]) {
[CodePushPackage downloadAndReplaceCurrentBundle:remoteBundleUrl];
}
}
/*
* This method is checks if a new status update exists (new version was installed,
* or an update failed) and return its details (version label, status).
*/
RCT_EXPORT_METHOD(getNewStatusReport:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
{
if (needToReportRollback) {
needToReportRollback = NO;
NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults];
NSMutableArray *failedUpdates = [preferences objectForKey:FailedUpdatesKey];
if (failedUpdates) {
NSDictionary *lastFailedPackage = [failedUpdates lastObject];
if (lastFailedPackage) {
resolve([CodePushTelemetryManager getRollbackReport:lastFailedPackage]);
return;
}
}
} else if (_isFirstRunAfterUpdate) {
NSError *error;
NSDictionary *currentPackage = [CodePushPackage getCurrentPackage:&error];
if (!error && currentPackage) {
resolve([CodePushTelemetryManager getUpdateReport:currentPackage]);
return;
}
} else if (isRunningBinaryVersion) {
NSString *appVersion = [[CodePushConfig current] appVersion];
resolve([CodePushTelemetryManager getBinaryUpdateReport:appVersion]);
return;
} else {
NSDictionary *retryStatusReport = [CodePushTelemetryManager getRetryStatusReport];
if (retryStatusReport) {
resolve(retryStatusReport);
return;
}
}
resolve(nil);
}
RCT_EXPORT_METHOD(recordStatusReported:(NSDictionary *)statusReport)
{
[CodePushTelemetryManager recordStatusReported:statusReport];
}
RCT_EXPORT_METHOD(saveStatusReportForRetry:(NSDictionary *)statusReport)
{
[CodePushTelemetryManager saveStatusReportForRetry:statusReport];
}
#pragma mark - RCTFrameUpdateObserver Methods
- (void)didUpdateFrame:(RCTFrameUpdate *)update
{
if (!_didUpdateProgress) {
return;
}
[self dispatchDownloadProgressEvent];
_didUpdateProgress = NO;
}
@end
================================================
FILE: ios/CodePush/CodePushConfig.m
================================================
#import "CodePush.h"
#import
@implementation CodePushConfig {
NSMutableDictionary *_configDictionary;
}
static CodePushConfig *_currentConfig;
static NSString * const AppVersionConfigKey = @"appVersion";
static NSString * const BuildVersionConfigKey = @"buildVersion";
static NSString * const ClientUniqueIDConfigKey = @"clientUniqueId";
static NSString * const DeploymentKeyConfigKey = @"deploymentKey";
static NSString * const ServerURLConfigKey = @"serverUrl";
static NSString * const PublicKeyKey = @"publicKey";
+ (instancetype)current
{
return _currentConfig;
}
+ (void)initialize
{
if (self == [CodePushConfig class]) {
_currentConfig = [[CodePushConfig alloc] init];
}
}
- (instancetype)init
{
self = [super init];
NSDictionary *infoDictionary = [[NSBundle mainBundle] infoDictionary];
NSString *appVersion = [infoDictionary objectForKey:@"CFBundleShortVersionString"];
NSString *buildVersion = [infoDictionary objectForKey:(NSString *)kCFBundleVersionKey];
NSString *deploymentKey = [infoDictionary objectForKey:@"CodePushDeploymentKey"];
NSString *serverURL = [infoDictionary objectForKey:@"CodePushServerURL"];
NSString *publicKey = [infoDictionary objectForKey:@"CodePushPublicKey"];
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
NSString *clientUniqueId = [userDefaults stringForKey:ClientUniqueIDConfigKey];
if (clientUniqueId == nil) {
clientUniqueId = [[[UIDevice currentDevice] identifierForVendor] UUIDString];
[userDefaults setObject:clientUniqueId forKey:ClientUniqueIDConfigKey];
[userDefaults synchronize];
}
if (!serverURL) {
serverURL = @"https://codepush.appcenter.ms/";
}
_configDictionary = [NSMutableDictionary dictionary];
if (appVersion) [_configDictionary setObject:appVersion forKey:AppVersionConfigKey];
if (buildVersion) [_configDictionary setObject:buildVersion forKey:BuildVersionConfigKey];
if (serverURL) [_configDictionary setObject:serverURL forKey:ServerURLConfigKey];
if (clientUniqueId) [_configDictionary setObject:clientUniqueId forKey:ClientUniqueIDConfigKey];
if (deploymentKey) [_configDictionary setObject:deploymentKey forKey:DeploymentKeyConfigKey];
if (publicKey) [_configDictionary setObject:publicKey forKey:PublicKeyKey];
return self;
}
- (NSString *)appVersion
{
return [_configDictionary objectForKey:AppVersionConfigKey];
}
- (NSString *)buildVersion
{
return [_configDictionary objectForKey:BuildVersionConfigKey];
}
- (NSDictionary *)configuration
{
return _configDictionary;
}
- (NSString *)deploymentKey
{
return [_configDictionary objectForKey:DeploymentKeyConfigKey];
}
- (NSString *)serverURL
{
return [_configDictionary objectForKey:ServerURLConfigKey];
}
- (NSString *)clientUniqueId
{
return [_configDictionary objectForKey:ClientUniqueIDConfigKey];
}
- (NSString *)publicKey
{
return [_configDictionary objectForKey:PublicKeyKey];
}
- (void)setAppVersion:(NSString *)appVersion
{
[_configDictionary setValue:appVersion forKey:AppVersionConfigKey];
}
- (void)setDeploymentKey:(NSString *)deploymentKey
{
[_configDictionary setValue:deploymentKey forKey:DeploymentKeyConfigKey];
}
- (void)setServerURL:(NSString *)serverURL
{
[_configDictionary setValue:serverURL forKey:ServerURLConfigKey];
}
//no setter for PublicKey, because it's need to be hard coded within Info.plist for safety
@end
================================================
FILE: ios/CodePush/CodePushDownloadHandler.m
================================================
#import "CodePush.h"
@implementation CodePushDownloadHandler {
// Header chars used to determine if the file is a zip.
char _header[4];
}
- (id)init:(NSString *)downloadFilePath
operationQueue:(dispatch_queue_t)operationQueue
progressCallback:(void (^)(long long, long long))progressCallback
doneCallback:(void (^)(BOOL))doneCallback
failCallback:(void (^)(NSError *err))failCallback {
self.outputFileStream = [NSOutputStream outputStreamToFileAtPath:downloadFilePath
append:NO];
self.receivedContentLength = 0;
self.operationQueue = operationQueue;
self.progressCallback = progressCallback;
self.doneCallback = doneCallback;
self.failCallback = failCallback;
return self;
}
- (void)download:(NSString *)url {
self.downloadUrl = url;
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:url]
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:60.0];
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request
delegate:self
startImmediately:NO];
if ([NSOperationQueue instancesRespondToSelector:@selector(setUnderlyingQueue:)]) {
NSOperationQueue *delegateQueue = [NSOperationQueue new];
delegateQueue.underlyingQueue = self.operationQueue;
[connection setDelegateQueue:delegateQueue];
} else {
[connection scheduleInRunLoop:[NSRunLoop mainRunLoop]
forMode:NSDefaultRunLoopMode];
}
[connection start];
}
#pragma mark NSURLConnection Delegate Methods
- (NSCachedURLResponse *)connection:(NSURLConnection *)connection
willCacheResponse:(NSCachedURLResponse*)cachedResponse {
// Return nil to indicate not necessary to store a cached response for this connection
return nil;
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
NSInteger statusCode = [(NSHTTPURLResponse *)response statusCode];
if (statusCode >= 400) {
[self.outputFileStream close];
[connection cancel];
NSError *err = [CodePushErrorUtils errorWithMessage:[NSString stringWithFormat: @"Received %ld response from %@", (long)statusCode, self.downloadUrl]];
self.failCallback(err);
return;
}
}
self.expectedContentLength = response.expectedContentLength;
[self.outputFileStream open];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
if (self.receivedContentLength < 4) {
for (int i = 0; i < [data length]; i++) {
int headerOffset = (int)self.receivedContentLength + i;
if (headerOffset >= 4) {
break;
}
const char *bytes = [data bytes];
_header[headerOffset] = bytes[i];
}
}
self.receivedContentLength = self.receivedContentLength + [data length];
NSInteger bytesLeft = [data length];
do {
NSInteger bytesWritten = [self.outputFileStream write:[data bytes]
maxLength:bytesLeft];
if (bytesWritten == -1) {
break;
}
bytesLeft -= bytesWritten;
} while (bytesLeft > 0);
self.progressCallback(self.expectedContentLength, self.receivedContentLength);
// bytesLeft should not be negative.
assert(bytesLeft >= 0);
if (bytesLeft) {
[self.outputFileStream close];
[connection cancel];
self.failCallback([self.outputFileStream streamError]);
}
}
- (void)connection:(NSURLConnection*)connection didFailWithError:(NSError*)error
{
[self.outputFileStream close];
self.failCallback(error);
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
[self.outputFileStream close];
if (self.receivedContentLength < 1) {
NSError *err = [CodePushErrorUtils errorWithMessage:[NSString stringWithFormat:@"Received empty response from %@", self.downloadUrl]];
self.failCallback(err);
return;
}
// expectedContentLength might be -1 when NSURLConnection don't know the length(e.g. response encode with gzip)
if (self.expectedContentLength > 0) {
// We should have received all of the bytes if this is called.
assert(self.receivedContentLength == self.expectedContentLength);
}
BOOL isZip = _header[0] == 'P' && _header[1] == 'K' && _header[2] == 3 && _header[3] == 4;
self.doneCallback(isZip);
}
@end
================================================
FILE: ios/CodePush/CodePushErrorUtils.m
================================================
#import "CodePush.h"
@implementation CodePushErrorUtils
static NSString *const CodePushErrorDomain = @"CodePushError";
static const int CodePushErrorCode = -1;
+ (NSError *)errorWithMessage:(NSString *)errorMessage
{
return [NSError errorWithDomain:CodePushErrorDomain
code:CodePushErrorCode
userInfo:@{ NSLocalizedDescriptionKey: NSLocalizedString(errorMessage, nil) }];
}
+ (BOOL)isCodePushError:(NSError *)err
{
return err != nil && [CodePushErrorDomain isEqualToString:err.domain];
}
@end
================================================
FILE: ios/CodePush/CodePushPackage.m
================================================
#import "CodePush.h"
#if __has_include()
#import
#else
#import "SSZipArchive.h"
#endif
@implementation CodePushPackage
#pragma mark - Private constants
static NSString *const DiffManifestFileName = @"hotcodepush.json";
static NSString *const DownloadFileName = @"download.zip";
static NSString *const RelativeBundlePathKey = @"bundlePath";
static NSString *const StatusFile = @"codepush.json";
static NSString *const UpdateBundleFileName = @"app.jsbundle";
static NSString *const UpdateMetadataFileName = @"app.json";
static NSString *const UnzippedFolderName = @"unzipped";
#pragma mark - Public methods
+ (void)clearUpdates
{
[[NSFileManager defaultManager] removeItemAtPath:[self getCodePushPath] error:nil];
}
+ (void)downloadAndReplaceCurrentBundle:(NSString *)remoteBundleUrl
{
NSURL *urlRequest = [NSURL URLWithString:remoteBundleUrl];
NSError *error = nil;
NSString *downloadedBundle = [NSString stringWithContentsOfURL:urlRequest
encoding:NSUTF8StringEncoding
error:&error];
if (error) {
CPLog(@"Error downloading from URL %@", remoteBundleUrl);
} else {
NSString *currentPackageBundlePath = [self getCurrentPackageBundlePath:&error];
[downloadedBundle writeToFile:currentPackageBundlePath
atomically:YES
encoding:NSUTF8StringEncoding
error:&error];
}
}
+ (void)downloadPackage:(NSDictionary *)updatePackage
expectedBundleFileName:(NSString *)expectedBundleFileName
publicKey:(NSString *)publicKey
operationQueue:(dispatch_queue_t)operationQueue
progressCallback:(void (^)(long long, long long))progressCallback
doneCallback:(void (^)())doneCallback
failCallback:(void (^)(NSError *err))failCallback
{
NSString *newUpdateHash = updatePackage[@"packageHash"];
NSString *newUpdateFolderPath = [self getPackageFolderPath:newUpdateHash];
NSString *newUpdateMetadataPath = [newUpdateFolderPath stringByAppendingPathComponent:UpdateMetadataFileName];
NSError *error;
if ([[NSFileManager defaultManager] fileExistsAtPath:newUpdateFolderPath]) {
// This removes any stale data in newUpdateFolderPath that could have been left
// uncleared due to a crash or error during the download or install process.
[[NSFileManager defaultManager] removeItemAtPath:newUpdateFolderPath
error:&error];
} else if (![[NSFileManager defaultManager] fileExistsAtPath:[self getCodePushPath]]) {
[[NSFileManager defaultManager] createDirectoryAtPath:[self getCodePushPath]
withIntermediateDirectories:YES
attributes:nil
error:&error];
// Ensure that none of the CodePush updates we store on disk are
// ever included in the end users iTunes and/or iCloud backups
NSURL *codePushURL = [NSURL fileURLWithPath:[self getCodePushPath]];
[codePushURL setResourceValue:@YES forKey:NSURLIsExcludedFromBackupKey error:nil];
}
if (error) {
return failCallback(error);
}
NSString *downloadFilePath = [self getDownloadFilePath];
NSString *bundleFilePath = [newUpdateFolderPath stringByAppendingPathComponent:UpdateBundleFileName];
CodePushDownloadHandler *downloadHandler = [[CodePushDownloadHandler alloc]
init:downloadFilePath
operationQueue:operationQueue
progressCallback:progressCallback
doneCallback:^(BOOL isZip) {
NSError *error = nil;
NSString * unzippedFolderPath = [CodePushPackage getUnzippedFolderPath];
NSMutableDictionary * mutableUpdatePackage = [updatePackage mutableCopy];
if (isZip) {
if ([[NSFileManager defaultManager] fileExistsAtPath:unzippedFolderPath]) {
// This removes any unzipped download data that could have been left
// uncleared due to a crash or error during the download process.
[[NSFileManager defaultManager] removeItemAtPath:unzippedFolderPath
error:&error];
if (error) {
failCallback(error);
return;
}
}
NSError *nonFailingError = nil;
[SSZipArchive unzipFileAtPath:downloadFilePath
toDestination:unzippedFolderPath];
[[NSFileManager defaultManager] removeItemAtPath:downloadFilePath
error:&nonFailingError];
if (nonFailingError) {
CPLog(@"Error deleting downloaded file: %@", nonFailingError);
nonFailingError = nil;
}
NSString *diffManifestFilePath = [unzippedFolderPath stringByAppendingPathComponent:DiffManifestFileName];
BOOL isDiffUpdate = [[NSFileManager defaultManager] fileExistsAtPath:diffManifestFilePath];
if (isDiffUpdate) {
// Copy the current package to the new package.
NSString *currentPackageFolderPath = [self getCurrentPackageFolderPath:&error];
if (error) {
failCallback(error);
return;
}
if (currentPackageFolderPath == nil) {
// Currently running the binary version, copy files from the bundled resources
NSString *newUpdateCodePushPath = [newUpdateFolderPath stringByAppendingPathComponent:[CodePushUpdateUtils manifestFolderPrefix]];
[[NSFileManager defaultManager] createDirectoryAtPath:newUpdateCodePushPath
withIntermediateDirectories:YES
attributes:nil
error:&error];
if (error) {
failCallback(error);
return;
}
[[NSFileManager defaultManager] copyItemAtPath:[CodePush bundleAssetsPath]
toPath:[newUpdateCodePushPath stringByAppendingPathComponent:[CodePushUpdateUtils assetsFolderName]]
error:&error];
if (error) {
failCallback(error);
return;
}
[[NSFileManager defaultManager] copyItemAtPath:[[CodePush binaryBundleURL] path]
toPath:[newUpdateCodePushPath stringByAppendingPathComponent:[[CodePush binaryBundleURL] lastPathComponent]]
error:&error];
if (error) {
failCallback(error);
return;
}
} else {
[[NSFileManager defaultManager] copyItemAtPath:currentPackageFolderPath
toPath:newUpdateFolderPath
error:&error];
if (error) {
failCallback(error);
return;
}
}
// Delete files mentioned in the manifest.
NSString *manifestContent = [NSString stringWithContentsOfFile:diffManifestFilePath
encoding:NSUTF8StringEncoding
error:&error];
if (error) {
failCallback(error);
return;
}
NSData *data = [manifestContent dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *manifestJSON = [NSJSONSerialization JSONObjectWithData:data
options:kNilOptions
error:&error];
NSArray *deletedFiles = manifestJSON[@"deletedFiles"];
for (NSString *deletedFileName in deletedFiles) {
NSString *absoluteDeletedFilePath = [newUpdateFolderPath stringByAppendingPathComponent:deletedFileName];
if ([[NSFileManager defaultManager] fileExistsAtPath:absoluteDeletedFilePath]) {
[[NSFileManager defaultManager] removeItemAtPath:absoluteDeletedFilePath
error:&error];
if (error) {
failCallback(error);
return;
}
}
}
[[NSFileManager defaultManager] removeItemAtPath:diffManifestFilePath
error:&error];
if (error) {
failCallback(error);
return;
}
}
[CodePushUpdateUtils copyEntriesInFolder:unzippedFolderPath
destFolder:newUpdateFolderPath
error:&error];
if (error) {
failCallback(error);
return;
}
[[NSFileManager defaultManager] removeItemAtPath:unzippedFolderPath
error:&nonFailingError];
if (nonFailingError) {
CPLog(@"Error deleting downloaded file: %@", nonFailingError);
nonFailingError = nil;
}
NSString *relativeBundlePath = [CodePushUpdateUtils findMainBundleInFolder:newUpdateFolderPath
expectedFileName:expectedBundleFileName
error:&error];
if (error) {
failCallback(error);
return;
}
if (relativeBundlePath) {
[mutableUpdatePackage setValue:relativeBundlePath forKey:RelativeBundlePathKey];
} else {
NSString *errorMessage = [NSString stringWithFormat:@"Update is invalid - A JS bundle file named \"%@\" could not be found within the downloaded contents. Please ensure that your app is syncing with the correct deployment and that you are releasing your CodePush updates using the exact same JS bundle file name that was shipped with your app's binary.", expectedBundleFileName];
error = [CodePushErrorUtils errorWithMessage:errorMessage];
failCallback(error);
return;
}
if ([[NSFileManager defaultManager] fileExistsAtPath:newUpdateMetadataPath]) {
[[NSFileManager defaultManager] removeItemAtPath:newUpdateMetadataPath
error:&error];
if (error) {
failCallback(error);
return;
}
}
CPLog((isDiffUpdate) ? @"Applying diff update." : @"Applying full update.");
BOOL isSignatureVerificationEnabled = (publicKey != nil);
NSString *signatureFilePath = [CodePushUpdateUtils getSignatureFilePath:newUpdateFolderPath];
BOOL isSignatureAppearedInBundle = [[NSFileManager defaultManager] fileExistsAtPath:signatureFilePath];
if (isSignatureVerificationEnabled) {
if (isSignatureAppearedInBundle) {
if (![CodePushUpdateUtils verifyFolderHash:newUpdateFolderPath
expectedHash:newUpdateHash
error:&error]) {
CPLog(@"The update contents failed the data integrity check.");
if (!error) {
error = [CodePushErrorUtils errorWithMessage:@"The update contents failed the data integrity check."];
}
failCallback(error);
return;
} else {
CPLog(@"The update contents succeeded the data integrity check.");
}
BOOL isSignatureValid = [CodePushUpdateUtils verifyUpdateSignatureFor:newUpdateFolderPath
expectedHash:newUpdateHash
withPublicKey:publicKey
error:&error];
if (!isSignatureValid) {
CPLog(@"The update contents failed code signing check.");
if (!error) {
error = [CodePushErrorUtils errorWithMessage:@"The update contents failed code signing check."];
}
failCallback(error);
return;
} else {
CPLog(@"The update contents succeeded the code signing check.");
}
} else {
error = [CodePushErrorUtils errorWithMessage:
@"Error! Public key was provided but there is no JWT signature within app bundle to verify " \
"Possible reasons, why that might happen: \n" \
"1. You've been released CodePush bundle update using version of CodePush CLI that is not support code signing.\n" \
"2. You've been released CodePush bundle update without providing --privateKeyPath option."];
failCallback(error);
return;
}
} else {
BOOL needToVerifyHash;
if (isSignatureAppearedInBundle) {
CPLog(@"Warning! JWT signature exists in codepush update but code integrity check couldn't be performed" \
" because there is no public key configured. " \
"Please ensure that public key is properly configured within your application.");
needToVerifyHash = true;
} else {
needToVerifyHash = isDiffUpdate;
}
if(needToVerifyHash){
if (![CodePushUpdateUtils verifyFolderHash:newUpdateFolderPath
expectedHash:newUpdateHash
error:&error]) {
CPLog(@"The update contents failed the data integrity check.");
if (!error) {
error = [CodePushErrorUtils errorWithMessage:@"The update contents failed the data integrity check."];
}
failCallback(error);
return;
} else {
CPLog(@"The update contents succeeded the data integrity check.");
}
}
}
} else {
[[NSFileManager defaultManager] createDirectoryAtPath:newUpdateFolderPath
withIntermediateDirectories:YES
attributes:nil
error:&error];
[[NSFileManager defaultManager] moveItemAtPath:downloadFilePath
toPath:bundleFilePath
error:&error];
if (error) {
failCallback(error);
return;
}
}
NSData *updateSerializedData = [NSJSONSerialization dataWithJSONObject:mutableUpdatePackage
options:0
error:&error];
NSString *packageJsonString = [[NSString alloc] initWithData:updateSerializedData
encoding:NSUTF8StringEncoding];
[packageJsonString writeToFile:newUpdateMetadataPath
atomically:YES
encoding:NSUTF8StringEncoding
error:&error];
if (error) {
failCallback(error);
} else {
doneCallback();
}
}
failCallback:failCallback];
[downloadHandler download:updatePackage[@"downloadUrl"]];
}
+ (NSString *)getCodePushPath
{
NSString* codePushPath = [[CodePush getApplicationSupportDirectory] stringByAppendingPathComponent:@"CodePush"];
if ([CodePush isUsingTestConfiguration]) {
codePushPath = [codePushPath stringByAppendingPathComponent:@"TestPackages"];
}
return codePushPath;
}
+ (NSDictionary *)getCurrentPackage:(NSError **)error
{
NSString *packageHash = [CodePushPackage getCurrentPackageHash:error];
if (!packageHash) {
return nil;
}
return [CodePushPackage getPackage:packageHash error:error];
}
+ (NSString *)getCurrentPackageBundlePath:(NSError **)error
{
NSString *packageFolder = [self getCurrentPackageFolderPath:error];
if (!packageFolder) {
return nil;
}
NSDictionary *currentPackage = [self getCurrentPackage:error];
if (!currentPackage) {
return nil;
}
NSString *relativeBundlePath = [currentPackage objectForKey:RelativeBundlePathKey];
if (relativeBundlePath) {
return [packageFolder stringByAppendingPathComponent:relativeBundlePath];
} else {
return [packageFolder stringByAppendingPathComponent:UpdateBundleFileName];
}
}
+ (NSString *)getCurrentPackageHash:(NSError **)error
{
NSDictionary *info = [self getCurrentPackageInfo:error];
if (!info) {
return nil;
}
return info[@"currentPackage"];
}
+ (NSString *)getCurrentPackageFolderPath:(NSError **)error
{
NSDictionary *info = [self getCurrentPackageInfo:error];
if (!info) {
return nil;
}
NSString *packageHash = info[@"currentPackage"];
if (!packageHash) {
return nil;
}
return [self getPackageFolderPath:packageHash];
}
+ (NSMutableDictionary *)getCurrentPackageInfo:(NSError **)error
{
NSString *statusFilePath = [self getStatusFilePath];
if (![[NSFileManager defaultManager] fileExistsAtPath:statusFilePath]) {
return [NSMutableDictionary dictionary];
}
NSString *content = [NSString stringWithContentsOfFile:statusFilePath
encoding:NSUTF8StringEncoding
error:error];
if (!content) {
return nil;
}
NSData *data = [content dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary* json = [NSJSONSerialization JSONObjectWithData:data
options:kNilOptions
error:error];
if (!json) {
return nil;
}
return [json mutableCopy];
}
+ (NSString *)getDownloadFilePath
{
return [[self getCodePushPath] stringByAppendingPathComponent:DownloadFileName];
}
+ (NSDictionary *)getPackage:(NSString *)packageHash
error:(NSError **)error
{
NSString *updateDirectoryPath = [self getPackageFolderPath:packageHash];
NSString *updateMetadataFilePath = [updateDirectoryPath stringByAppendingPathComponent:UpdateMetadataFileName];
if (![[NSFileManager defaultManager] fileExistsAtPath:updateMetadataFilePath]) {
return nil;
}
NSString *updateMetadataString = [NSString stringWithContentsOfFile:updateMetadataFilePath
encoding:NSUTF8StringEncoding
error:error];
if (!updateMetadataString) {
return nil;
}
NSData *updateMetadata = [updateMetadataString dataUsingEncoding:NSUTF8StringEncoding];
return [NSJSONSerialization JSONObjectWithData:updateMetadata
options:kNilOptions
error:error];
}
+ (NSString *)getPackageFolderPath:(NSString *)packageHash
{
return [[self getCodePushPath] stringByAppendingPathComponent:packageHash];
}
+ (NSDictionary *)getPreviousPackage:(NSError **)error
{
NSString *packageHash = [self getPreviousPackageHash:error];
if (!packageHash) {
return nil;
}
return [CodePushPackage getPackage:packageHash error:error];
}
+ (NSString *)getPreviousPackageHash:(NSError **)error
{
NSDictionary *info = [self getCurrentPackageInfo:error];
if (!info) {
return nil;
}
return info[@"previousPackage"];
}
+ (NSString *)getStatusFilePath
{
return [[self getCodePushPath] stringByAppendingPathComponent:StatusFile];
}
+ (NSString *)getUnzippedFolderPath
{
return [[self getCodePushPath] stringByAppendingPathComponent:UnzippedFolderName];
}
+ (BOOL)installPackage:(NSDictionary *)updatePackage
removePendingUpdate:(BOOL)removePendingUpdate
error:(NSError **)error
{
NSString *packageHash = updatePackage[@"packageHash"];
NSMutableDictionary *info = [self getCurrentPackageInfo:error];
if (!info) {
return NO;
}
if (packageHash && [packageHash isEqualToString:info[@"currentPackage"]]) {
// The current package is already the one being installed, so we should no-op.
return YES;
}
if (removePendingUpdate) {
NSString *currentPackageFolderPath = [self getCurrentPackageFolderPath:error];
if (currentPackageFolderPath) {
// Error in deleting pending package will not cause the entire operation to fail.
NSError *deleteError;
[[NSFileManager defaultManager] removeItemAtPath:currentPackageFolderPath
error:&deleteError];
if (deleteError) {
CPLog(@"Error deleting pending package: %@", deleteError);
}
}
} else {
NSString *previousPackageHash = [self getPreviousPackageHash:error];
if (previousPackageHash && ![previousPackageHash isEqualToString:packageHash]) {
NSString *previousPackageFolderPath = [self getPackageFolderPath:previousPackageHash];
// Error in deleting old package will not cause the entire operation to fail.
NSError *deleteError;
[[NSFileManager defaultManager] removeItemAtPath:previousPackageFolderPath
error:&deleteError];
if (deleteError) {
CPLog(@"Error deleting old package: %@", deleteError);
}
}
[info setValue:info[@"currentPackage"] forKey:@"previousPackage"];
}
[info setValue:packageHash forKey:@"currentPackage"];
return [self updateCurrentPackageInfo:info
error:error];
}
+ (void)rollbackPackage
{
NSError *error;
NSMutableDictionary *info = [self getCurrentPackageInfo:&error];
if (!info) {
CPLog(@"Error getting current package info: %@", error);
return;
}
NSString *currentPackageFolderPath = [self getCurrentPackageFolderPath:&error];
if (!currentPackageFolderPath) {
CPLog(@"Error getting current package folder path: %@", error);
return;
}
NSError *deleteError;
BOOL result = [[NSFileManager defaultManager] removeItemAtPath:currentPackageFolderPath
error:&deleteError];
if (!result) {
CPLog(@"Error deleting current package contents at %@ error %@", currentPackageFolderPath, deleteError);
}
[info setValue:info[@"previousPackage"] forKey:@"currentPackage"];
[info removeObjectForKey:@"previousPackage"];
[self updateCurrentPackageInfo:info error:&error];
}
+ (BOOL)updateCurrentPackageInfo:(NSDictionary *)packageInfo
error:(NSError **)error
{
NSData *packageInfoData = [NSJSONSerialization dataWithJSONObject:packageInfo
options:0
error:error];
if (!packageInfoData) {
return NO;
}
NSString *packageInfoString = [[NSString alloc] initWithData:packageInfoData
encoding:NSUTF8StringEncoding];
BOOL result = [packageInfoString writeToFile:[self getStatusFilePath]
atomically:YES
encoding:NSUTF8StringEncoding
error:error];
if (!result) {
return NO;
}
return YES;
}
@end
================================================
FILE: ios/CodePush/CodePushTelemetryManager.m
================================================
#import "CodePush.h"
static NSString *const AppVersionKey = @"appVersion";
static NSString *const DeploymentFailed = @"DeploymentFailed";
static NSString *const DeploymentKeyKey = @"deploymentKey";
static NSString *const DeploymentSucceeded = @"DeploymentSucceeded";
static NSString *const LabelKey = @"label";
static NSString *const LastDeploymentReportKey = @"CODE_PUSH_LAST_DEPLOYMENT_REPORT";
static NSString *const PackageKey = @"package";
static NSString *const PreviousDeploymentKeyKey = @"previousDeploymentKey";
static NSString *const PreviousLabelOrAppVersionKey = @"previousLabelOrAppVersion";
static NSString *const RetryDeploymentReportKey = @"CODE_PUSH_RETRY_DEPLOYMENT_REPORT";
static NSString *const StatusKey = @"status";
@implementation CodePushTelemetryManager
+ (NSDictionary *)getBinaryUpdateReport:(NSString *)appVersion
{
NSString *previousStatusReportIdentifier = [self getPreviousStatusReportIdentifier];
if (previousStatusReportIdentifier == nil) {
[self clearRetryStatusReport];
return @{ AppVersionKey: appVersion };
} else if (![previousStatusReportIdentifier isEqualToString:appVersion]) {
if ([self isStatusReportIdentifierCodePushLabel:previousStatusReportIdentifier]) {
NSString *previousDeploymentKey = [self getDeploymentKeyFromStatusReportIdentifier:previousStatusReportIdentifier];
NSString *previousLabel = [self getVersionLabelFromStatusReportIdentifier:previousStatusReportIdentifier];
[self clearRetryStatusReport];
return @{
AppVersionKey: appVersion,
PreviousDeploymentKeyKey: previousDeploymentKey,
PreviousLabelOrAppVersionKey: previousLabel
};
} else {
[self clearRetryStatusReport];
// Previous status report was with a binary app version.
return @{
AppVersionKey: appVersion,
PreviousLabelOrAppVersionKey: previousStatusReportIdentifier
};
}
}
return nil;
}
+ (NSDictionary *)getRetryStatusReport
{
NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults];
NSDictionary *retryStatusReport = [preferences objectForKey:RetryDeploymentReportKey];
if (retryStatusReport) {
[self clearRetryStatusReport];
return retryStatusReport;
} else {
return nil;
}
}
+ (NSDictionary *)getRollbackReport:(NSDictionary *)lastFailedPackage
{
return @{
PackageKey: lastFailedPackage,
StatusKey: DeploymentFailed
};
}
+ (NSDictionary *)getUpdateReport:(NSDictionary *)currentPackage
{
NSString *currentPackageIdentifier = [self getPackageStatusReportIdentifier:currentPackage];
NSString *previousStatusReportIdentifier = [self getPreviousStatusReportIdentifier];
if (currentPackageIdentifier) {
if (previousStatusReportIdentifier == nil) {
[self clearRetryStatusReport];
return @{
PackageKey: currentPackage,
StatusKey: DeploymentSucceeded
};
} else if (![previousStatusReportIdentifier isEqualToString:currentPackageIdentifier]) {
[self clearRetryStatusReport];
if ([self isStatusReportIdentifierCodePushLabel:previousStatusReportIdentifier]) {
NSString *previousDeploymentKey = [self getDeploymentKeyFromStatusReportIdentifier:previousStatusReportIdentifier];
NSString *previousLabel = [self getVersionLabelFromStatusReportIdentifier:previousStatusReportIdentifier];
return @{
PackageKey: currentPackage,
StatusKey: DeploymentSucceeded,
PreviousDeploymentKeyKey: previousDeploymentKey,
PreviousLabelOrAppVersionKey: previousLabel
};
} else {
// Previous status report was with a binary app version.
return @{
PackageKey: currentPackage,
StatusKey: DeploymentSucceeded,
PreviousLabelOrAppVersionKey: previousStatusReportIdentifier
};
}
}
}
return nil;
}
+ (void)recordStatusReported:(NSDictionary *)statusReport
{
// We don't need to record rollback reports, so exit early if that's what was specified.
if ([DeploymentFailed isEqualToString:statusReport[StatusKey]]) {
return;
}
if (statusReport[AppVersionKey]) {
[self saveStatusReportedForIdentifier:statusReport[AppVersionKey]];
} else if (statusReport[PackageKey]) {
NSString *packageIdentifier = [self getPackageStatusReportIdentifier:statusReport[PackageKey]];
[self saveStatusReportedForIdentifier:packageIdentifier];
}
}
+ (void)saveStatusReportForRetry:(NSDictionary *)statusReport
{
NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults];
[preferences setValue:statusReport forKey:RetryDeploymentReportKey];
[preferences synchronize];
}
#pragma mark - private methods
+ (void)clearRetryStatusReport
{
NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults];
[preferences setValue:nil forKey:RetryDeploymentReportKey];
[preferences synchronize];
}
+ (NSString *)getDeploymentKeyFromStatusReportIdentifier:(NSString *)statusReportIdentifier
{
return [[statusReportIdentifier componentsSeparatedByString:@":"] firstObject];
}
+ (NSString *)getPackageStatusReportIdentifier:(NSDictionary *)package
{
// Because deploymentKeys can be dynamically switched, we use a
// combination of the deploymentKey and label as the packageIdentifier.
NSString *deploymentKey = [package objectForKey:DeploymentKeyKey];
NSString *label = [package objectForKey:LabelKey];
if (deploymentKey && label) {
return [[deploymentKey stringByAppendingString:@":"] stringByAppendingString:label];
} else {
return nil;
}
}
+ (NSString *)getPreviousStatusReportIdentifier
{
NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults];
NSString *sentStatusReportIdentifier = [preferences objectForKey:LastDeploymentReportKey];
return sentStatusReportIdentifier;
}
+ (NSString *)getVersionLabelFromStatusReportIdentifier:(NSString *)statusReportIdentifier
{
return [[statusReportIdentifier componentsSeparatedByString:@":"] lastObject];
}
+ (BOOL)isStatusReportIdentifierCodePushLabel:(NSString *)statusReportIdentifier
{
return statusReportIdentifier != nil && [statusReportIdentifier rangeOfString:@":"].location != NSNotFound;
}
+ (void)saveStatusReportedForIdentifier:(NSString *)appVersionOrPackageIdentifier
{
NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults];
[preferences setValue:appVersionOrPackageIdentifier forKey:LastDeploymentReportKey];
[preferences synchronize];
}
@end
================================================
FILE: ios/CodePush/CodePushUpdateUtils.m
================================================
#import "CodePush.h"
#include
#import "JWT.h"
@implementation CodePushUpdateUtils
NSString * const AssetsFolderName = @"assets";
NSString * const BinaryHashKey = @"CodePushBinaryHash";
NSString * const ManifestFolderPrefix = @"CodePush";
NSString * const BundleJWTFile = @".codepushrelease";
/*
Ignore list for hashing
*/
NSString * const IgnoreMacOSX= @"__MACOSX/";
NSString * const IgnoreDSStore = @".DS_Store";
NSString * const IgnoreCodePushMetadata = @".codepushrelease";
+ (BOOL)isHashIgnoredFor:(NSString *) relativePath
{
return [relativePath hasPrefix:IgnoreMacOSX]
|| [relativePath isEqualToString:IgnoreDSStore]
|| [relativePath hasSuffix:[NSString stringWithFormat:@"/%@", IgnoreDSStore]]
|| [relativePath isEqualToString:IgnoreCodePushMetadata]
|| [relativePath hasSuffix:[NSString stringWithFormat:@"/%@", IgnoreCodePushMetadata]];
}
+ (BOOL)addContentsOfFolderToManifest:(NSString *)folderPath
pathPrefix:(NSString *)pathPrefix
manifest:(NSMutableArray *)manifest
error:(NSError **)error
{
NSArray *folderFiles = [[NSFileManager defaultManager]
contentsOfDirectoryAtPath:folderPath
error:error];
if (!folderFiles) {
return NO;
}
for (NSString *fileName in folderFiles) {
NSString *fullFilePath = [folderPath stringByAppendingPathComponent:fileName];
NSString *relativePath = [pathPrefix stringByAppendingPathComponent:fileName];
if([self isHashIgnoredFor:relativePath]){
continue;
}
BOOL isDir = NO;
if ([[NSFileManager defaultManager] fileExistsAtPath:fullFilePath
isDirectory:&isDir] && isDir) {
BOOL result = [self addContentsOfFolderToManifest:fullFilePath
pathPrefix:relativePath
manifest:manifest
error:error];
if (!result) {
return NO;
}
} else {
NSData *fileContents = [NSData dataWithContentsOfFile:fullFilePath];
NSString *fileContentsHash = [self computeHashForData:fileContents];
[manifest addObject:[[relativePath stringByAppendingString:@":"] stringByAppendingString:fileContentsHash]];
}
}
return YES;
}
+ (void)addFileToManifest:(NSURL *)fileURL
manifest:(NSMutableArray *)manifest
{
if ([[NSFileManager defaultManager] fileExistsAtPath:[fileURL path]]) {
NSData *fileContents = [NSData dataWithContentsOfURL:fileURL];
NSString *fileContentsHash = [self computeHashForData:fileContents];
[manifest addObject:[NSString stringWithFormat:@"%@/%@:%@", [self manifestFolderPrefix], [fileURL lastPathComponent], fileContentsHash]];
}
}
+ (NSString *)computeFinalHashFromManifest:(NSMutableArray *)manifest
error:(NSError **)error
{
//sort manifest strings to make sure, that they are completely equal with manifest strings has been generated in cli!
NSArray *sortedManifest = [manifest sortedArrayUsingSelector:@selector(compare:)];
NSData *manifestData = [NSJSONSerialization dataWithJSONObject:sortedManifest
options:kNilOptions
error:error];
if (!manifestData) {
return nil;
}
NSString *manifestString = [[NSString alloc] initWithData:manifestData
encoding:NSUTF8StringEncoding];
// The JSON serialization turns path separators into "\/", e.g. "CodePush\/assets\/image.png"
manifestString = [manifestString stringByReplacingOccurrencesOfString:@"\\/"
withString:@"/"];
return [self computeHashForData:[NSData dataWithBytes:manifestString.UTF8String length:[manifestString lengthOfBytesUsingEncoding:NSUTF8StringEncoding]]];
}
+ (NSString *)computeHashForData:(NSData *)inputData
{
uint8_t digest[CC_SHA256_DIGEST_LENGTH];
CC_SHA256(inputData.bytes, (CC_LONG)inputData.length, digest);
NSMutableString* inputHash = [NSMutableString stringWithCapacity:CC_SHA256_DIGEST_LENGTH * 2];
for (int i = 0; i < CC_SHA256_DIGEST_LENGTH; i++) {
[inputHash appendFormat:@"%02x", digest[i]];
}
return inputHash;
}
+ (BOOL)copyEntriesInFolder:(NSString *)sourceFolder
destFolder:(NSString *)destFolder
error:(NSError **)error
{
NSArray *files = [[NSFileManager defaultManager]
contentsOfDirectoryAtPath:sourceFolder
error:error];
if (!files) {
return NO;
}
for (NSString *fileName in files) {
NSString * fullFilePath = [sourceFolder stringByAppendingPathComponent:fileName];
BOOL isDir = NO;
if ([[NSFileManager defaultManager] fileExistsAtPath:fullFilePath
isDirectory:&isDir] && isDir) {
NSString *nestedDestFolder = [destFolder stringByAppendingPathComponent:fileName];
BOOL result = [self copyEntriesInFolder:fullFilePath
destFolder:nestedDestFolder
error:error];
if (!result) {
return NO;
}
} else {
NSString *destFileName = [destFolder stringByAppendingPathComponent:fileName];
if ([[NSFileManager defaultManager] fileExistsAtPath:destFileName]) {
BOOL result = [[NSFileManager defaultManager] removeItemAtPath:destFileName error:error];
if (!result) {
return NO;
}
}
if (![[NSFileManager defaultManager] fileExistsAtPath:destFolder]) {
BOOL result = [[NSFileManager defaultManager] createDirectoryAtPath:destFolder
withIntermediateDirectories:YES
attributes:nil
error:error];
if (!result) {
return NO;
}
}
BOOL result = [[NSFileManager defaultManager] copyItemAtPath:fullFilePath toPath:destFileName error:error];
if (!result) {
return NO;
}
}
}
return YES;
}
+ (NSString *)findMainBundleInFolder:(NSString *)folderPath
expectedFileName:(NSString *)expectedFileName
error:(NSError **)error
{
NSArray* folderFiles = [[NSFileManager defaultManager]
contentsOfDirectoryAtPath:folderPath
error:error];
if (!folderFiles) {
return nil;
}
for (NSString *fileName in folderFiles) {
NSString *fullFilePath = [folderPath stringByAppendingPathComponent:fileName];
BOOL isDir = NO;
if ([[NSFileManager defaultManager] fileExistsAtPath:fullFilePath
isDirectory:&isDir] && isDir) {
NSString *mainBundlePathInFolder = [self findMainBundleInFolder:fullFilePath
expectedFileName:expectedFileName
error:error];
if (mainBundlePathInFolder) {
return [fileName stringByAppendingPathComponent:mainBundlePathInFolder];
}
} else if ([fileName isEqualToString:expectedFileName]) {
return fileName;
}
}
return nil;
}
+ (NSString *)assetsFolderName
{
return AssetsFolderName;
}
+ (NSString *)getHashForBinaryContents:(NSURL *)binaryBundleUrl
error:(NSError **)error
{
// Get the cached hash from user preferences if it exists.
NSString *binaryModifiedDate = [self modifiedDateStringOfFileAtURL:binaryBundleUrl];
NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults];
NSMutableDictionary *binaryHashDictionary = [preferences objectForKey:BinaryHashKey];
NSString *binaryHash = nil;
if (binaryHashDictionary != nil) {
binaryHash = [binaryHashDictionary objectForKey:binaryModifiedDate];
if (binaryHash == nil) {
[preferences removeObjectForKey:BinaryHashKey];
[preferences synchronize];
} else {
return binaryHash;
}
}
binaryHashDictionary = [NSMutableDictionary dictionary];
NSMutableArray *manifest = [NSMutableArray array];
// If the app is using assets, then add
// them to the generated content manifest.
NSString *assetsPath = [CodePush bundleAssetsPath];
if ([[NSFileManager defaultManager] fileExistsAtPath:assetsPath]) {
BOOL result = [self addContentsOfFolderToManifest:assetsPath
pathPrefix:[NSString stringWithFormat:@"%@/%@", [self manifestFolderPrefix], @"assets"]
manifest:manifest
error:error];
if (!result) {
return nil;
}
}
[self addFileToManifest:binaryBundleUrl manifest:manifest];
[self addFileToManifest:[binaryBundleUrl URLByAppendingPathExtension:@"meta"] manifest:manifest];
binaryHash = [self computeFinalHashFromManifest:manifest error:error];
// Cache the hash in user preferences. This assumes that the modified date for the
// JS bundle changes every time a new bundle is generated by the packager.
[binaryHashDictionary setObject:binaryHash forKey:binaryModifiedDate];
[preferences setObject:binaryHashDictionary forKey:BinaryHashKey];
[preferences synchronize];
return binaryHash;
}
+ (NSString *)manifestFolderPrefix
{
return ManifestFolderPrefix;
}
+ (NSString *)modifiedDateStringOfFileAtURL:(NSURL *)fileURL
{
if (fileURL != nil) {
NSDictionary *fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:[fileURL path] error:nil];
NSDate *modifiedDate = [fileAttributes objectForKey:NSFileModificationDate];
return [NSString stringWithFormat:@"%f", [modifiedDate timeIntervalSince1970]];
} else {
return nil;
}
}
+ (BOOL)verifyFolderHash:(NSString *)finalUpdateFolder
expectedHash:(NSString *)expectedHash
error:(NSError **)error
{
CPLog(@"Verifying hash for folder path: %@", finalUpdateFolder);
NSMutableArray *updateContentsManifest = [NSMutableArray array];
BOOL result = [self addContentsOfFolderToManifest:finalUpdateFolder
pathPrefix:@""
manifest:updateContentsManifest
error:error];
CPLog(@"Manifest string: %@", updateContentsManifest);
if (!result) {
return NO;
}
NSString *updateContentsManifestHash = [self computeFinalHashFromManifest:updateContentsManifest
error:error];
if (!updateContentsManifestHash) {
return NO;
}
CPLog(@"Expected hash: %@, actual hash: %@", expectedHash, updateContentsManifestHash);
return [updateContentsManifestHash isEqualToString:expectedHash];
}
// remove BEGIN / END tags and line breaks from public key string
+ (NSString *)getKeyValueFromPublicKeyString:(NSString *)publicKeyString
{
publicKeyString = [publicKeyString stringByReplacingOccurrencesOfString:@"-----BEGIN PUBLIC KEY-----\n"
withString:@""];
publicKeyString = [publicKeyString stringByReplacingOccurrencesOfString:@"-----END PUBLIC KEY-----"
withString:@""];
publicKeyString = [publicKeyString stringByReplacingOccurrencesOfString:@"\n"
withString:@""];
return publicKeyString;
}
+ (NSString *)getSignatureFilePath:(NSString *)updateFolderPath
{
return [NSString stringWithFormat:@"%@/%@/%@", updateFolderPath, ManifestFolderPrefix, BundleJWTFile];
}
+ (NSString *)getSignatureFor:(NSString *)folderPath
error:(NSError **)error
{
NSString *signatureFilePath = [self getSignatureFilePath:folderPath];
if ([[NSFileManager defaultManager] fileExistsAtPath:signatureFilePath]) {
return [NSString stringWithContentsOfFile:signatureFilePath encoding:NSUTF8StringEncoding error:error];
} else {
*error = [CodePushErrorUtils errorWithMessage:[NSString stringWithFormat: @"Cannot find signature at %@", signatureFilePath]];
return nil;
}
}
+ (NSDictionary *) verifyAndDecodeJWT:(NSString *)jwt
withPublicKey:(NSString *)publicKey
error:(NSError **)error
{
id verifyDataHolder = [JWTAlgorithmRSFamilyDataHolder new].keyExtractorType([JWTCryptoKeyExtractor publicKeyWithPEMBase64].type).algorithmName(@"RS256").secret(publicKey);
JWTCodingBuilder *verifyBuilder = [JWTDecodingBuilder decodeMessage:jwt].addHolder(verifyDataHolder);
JWTCodingResultType *verifyResult = verifyBuilder.result;
if (verifyResult.successResult) {
return verifyResult.successResult.payload;
}
else {
*error = verifyResult.errorResult.error;
return nil;
}
}
+ (BOOL)verifyUpdateSignatureFor:(NSString *)folderPath
expectedHash:(NSString *)newUpdateHash
withPublicKey:(NSString *)publicKeyString
error:(NSError **)error
{
NSLog(@"Verifying signature for folder path: %@", folderPath);
NSString *publicKey = [self getKeyValueFromPublicKeyString: publicKeyString];
NSError *signatureVerificationError;
NSString *signature = [self getSignatureFor: folderPath
error: &signatureVerificationError];
if (signatureVerificationError) {
CPLog(@"The update could not be verified because no signature was found. %@", signatureVerificationError);
*error = signatureVerificationError;
return false;
}
NSError *payloadDecodingError;
NSDictionary *envelopedPayload = [self verifyAndDecodeJWT:signature withPublicKey:publicKey error:&payloadDecodingError];
if(payloadDecodingError){
CPLog(@"The update could not be verified because it was not signed by a trusted party. %@", payloadDecodingError);
*error = payloadDecodingError;
return false;
}
CPLog(@"JWT signature verification succeeded, payload content: %@", envelopedPayload);
if(![envelopedPayload objectForKey:@"contentHash"]){
CPLog(@"The update could not be verified because the signature did not specify a content hash.");
return false;
}
NSString *contentHash = envelopedPayload[@"contentHash"];
return [contentHash isEqualToString:newUpdateHash];
}
@end
================================================
FILE: ios/CodePush/CodePushUtils.m
================================================
#import "CodePush.h"
void CPLog(NSString *formatString, ...) {
va_list args;
va_start(args, formatString);
NSString *prependedFormatString = [NSString stringWithFormat:@"\n[CodePush] %@", formatString];
NSLogv(prependedFormatString, args);
va_end(args);
}
================================================
FILE: ios/CodePush/JWT/Core/Algorithms/Base/JWTAlgorithm.h
================================================
//
// JWTAlgorithm.h
// JWT
//
// Created by Klaas Pieter Annema on 31-05-13.
// Copyright (c) 2013 Karma. All rights reserved.
//
#import
#import "JWTDeprecations.h"
@protocol JWTAlgorithm
@required
/**
Signs data using provided secret data.
@param hash The data to sign.
@param key The secret to use for signing.
@param error The inout error.
*/
- (NSData *)signHash:(NSData *)hash key:(NSData *)key error:(NSError *__autoreleasing*)error;
/**
Verifies data using.
@param hash The data to sign.
@param signature The secret to use for signing.
@param error The inout error.
*/
- (BOOL)verifyHash:(NSData *)hash signature:(NSData *)signature key:(NSData *)key error:(NSError *__autoreleasing*)error;
//@required
@property (nonatomic, readonly, copy) NSString *name;
/**
Encodes and encrypts the provided payload using the provided secret key
@param theString The string to encode
@param theSecret The secret to use for encryption
@return An NSData object containing the encrypted payload, or nil if something went wrong.
*/
- (NSData *)encodePayload:(NSString *)theString withSecret:(NSString *)theSecret __deprecated_and_will_be_removed_in_release_version(JWTVersion_3_0_0);
/**
Verifies the provided signature using the signed input and verification key
@param input The header and payload encoded string
@param signature The JWT provided signature
@param verificationKey The key to use for verifying the signature
@return YES if the provided signature is valid, NO otherwise
*/
- (BOOL)verifySignedInput:(NSString *)input withSignature:(NSString *)signature verificationKey:(NSString *)verificationKey __deprecated_and_will_be_removed_in_release_version(JWTVersion_3_0_0);
@optional
/**
Encodes and encrypts the provided payload using the provided secret key
@param theStringData The data to encode
@param theSecretData The secret data to use for encryption
@return An NSData object containing the encrypted payload, or nil if something went wrong.
*/
- (NSData *)encodePayloadData:(NSData *)theStringData withSecret:(NSData *)theSecretData;
/**
Verifies the provided signature using the signed input and verification key (as data)
@param input The header and payload encoded string
@param signature The JWT provided signature
@param verificationKeyData The key data to use for verifying the signature
@return YES if the provided signature is valid, NO otherwise
*/
- (BOOL)verifySignedInput:(NSString *)input withSignature:(NSString *)signature verificationKeyData:(NSData *)verificationKeyData;
@end
================================================
FILE: ios/CodePush/JWT/Core/Algorithms/Base/JWTAlgorithmFactory.h
================================================
//
// JWTAlgorithmFactory.h
// JWT
//
// Created by Lobanov Dmitry on 07.10.15.
// Copyright © 2015 Karma. All rights reserved.
//
#import