Skip to content
This repository was archived by the owner on May 20, 2025. It is now read-only.

Commit 074d372

Browse files
author
max-mironov
committed
2 parents 64a6f9c + 3808b57 commit 074d372

File tree

6 files changed

+92
-51
lines changed

6 files changed

+92
-51
lines changed

CodePush.js

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import log from "./logging";
88
let NativeCodePush = require("react-native").NativeModules.CodePush;
99
const PackageMixins = require("./package-mixins")(NativeCodePush);
1010

11-
async function checkForUpdate(deploymentKey = null) {
11+
async function checkForUpdate(deploymentKey = null, handleBinaryVersionMismatchCallback = null) {
1212
/*
1313
* Before we ask the server if an update exists, we
1414
* need to retrieve three pieces of information from the
@@ -19,7 +19,6 @@ async function checkForUpdate(deploymentKey = null) {
1919
* different from the CodePush update they have already installed.
2020
*/
2121
const nativeConfig = await getConfiguration();
22-
2322
/*
2423
* If a deployment key was explicitly provided,
2524
* then let's override the one we retrieved
@@ -76,6 +75,9 @@ async function checkForUpdate(deploymentKey = null) {
7675
(!localPackage || localPackage._isDebugOnly) && config.packageHash === update.packageHash) {
7776
if (update && update.updateAppVersion) {
7877
log("An update is available but it is not targeting the binary version of your app.");
78+
if (handleBinaryVersionMismatchCallback && typeof handleBinaryVersionMismatchCallback === "function") {
79+
handleBinaryVersionMismatchCallback(update)
80+
}
7981
}
8082

8183
return null;
@@ -237,7 +239,7 @@ const sync = (() => {
237239
let syncInProgress = false;
238240
const setSyncCompleted = () => { syncInProgress = false; };
239241

240-
return (options = {}, syncStatusChangeCallback, downloadProgressCallback) => {
242+
return (options = {}, syncStatusChangeCallback, downloadProgressCallback, handleBinaryVersionMismatchCallback) => {
241243
let syncStatusCallbackWithTryCatch, downloadProgressCallbackkWithTryCatch;
242244
if (typeof syncStatusChangeCallback === "function") {
243245
syncStatusCallbackWithTryCatch = (...args) => {
@@ -267,7 +269,7 @@ const sync = (() => {
267269
}
268270

269271
syncInProgress = true;
270-
const syncPromise = syncInternal(options, syncStatusCallbackWithTryCatch, downloadProgressCallbackkWithTryCatch);
272+
const syncPromise = syncInternal(options, syncStatusCallbackWithTryCatch, downloadProgressCallbackkWithTryCatch, handleBinaryVersionMismatchCallback);
271273
syncPromise
272274
.then(setSyncCompleted)
273275
.catch(setSyncCompleted);
@@ -285,7 +287,7 @@ const sync = (() => {
285287
* releases, and displaying a standard confirmation UI to the end-user
286288
* when an update is available.
287289
*/
288-
async function syncInternal(options = {}, syncStatusChangeCallback, downloadProgressCallback) {
290+
async function syncInternal(options = {}, syncStatusChangeCallback, downloadProgressCallback, handleBinaryVersionMismatchCallback) {
289291
let resolvedInstallMode;
290292
const syncOptions = {
291293
deploymentKey: null,
@@ -340,7 +342,7 @@ async function syncInternal(options = {}, syncStatusChangeCallback, downloadProg
340342
await CodePush.notifyApplicationReady();
341343

342344
syncStatusChangeCallback(CodePush.SyncStatus.CHECKING_FOR_UPDATE);
343-
const remotePackage = await checkForUpdate(syncOptions.deploymentKey);
345+
const remotePackage = await checkForUpdate(syncOptions.deploymentKey, handleBinaryVersionMismatchCallback);
344346

345347
const doDownloadAndInstall = async () => {
346348
syncStatusChangeCallback(CodePush.SyncStatus.DOWNLOADING_PACKAGE);
@@ -472,7 +474,15 @@ function codePushify(options = {}) {
472474
}
473475
}
474476

475-
CodePush.sync(options, syncStatusCallback, downloadProgressCallback);
477+
let handleBinaryVersionMismatchCallback;
478+
if (rootComponentInstance && rootComponentInstance.codePushOnBinaryVersionMismatch) {
479+
handleBinaryVersionMismatchCallback = rootComponentInstance.codePushOnBinaryVersionMismatch;
480+
if (rootComponentInstance instanceof React.Component) {
481+
handleBinaryVersionMismatchCallback = handleBinaryVersionMismatchCallback.bind(rootComponentInstance);
482+
}
483+
}
484+
485+
CodePush.sync(options, syncStatusCallback, downloadProgressCallback, handleBinaryVersionMismatchCallback);
476486
if (options.checkFrequency === CodePush.CheckFrequency.ON_APP_RESUME) {
477487
ReactNative.AppState.addEventListener("change", (newState) => {
478488
newState === "active" && CodePush.sync(options, syncStatusCallback, downloadProgressCallback);

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,8 @@ We try our best to maintain backwards compatability of our plugin with previous
6161
| v0.40-v0.42 | v1.17 *(RN refactored iOS header files)* |
6262
| v0.43-v0.44 | v2.0+ *(RN refactored uimanager dependencies)* |
6363
| v0.45 | v3.0+ *(RN refactored instance manager code)* |
64-
| v0.46+ | TBD :) 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.
64+
| v0.46 | v4.0+ *(RN refactored js bundle loader code)* |
65+
| v0.47+ | TBD :) 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.
6566

6667
### Supported Components
6768

android/app/src/main/java/com/microsoft/codepush/react/CodePush.java

Lines changed: 50 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ long getBinaryResourcesModifiedTime() {
105105
int codePushApkBuildTimeId = this.mContext.getResources().getIdentifier(CodePushConstants.CODE_PUSH_APK_BUILD_TIME_KEY, "string", packageName);
106106
String codePushApkBuildTime = this.mContext.getResources().getString(codePushApkBuildTimeId);
107107
return Long.parseLong(codePushApkBuildTime);
108-
} catch (Exception e) {
108+
} catch (Exception e) {
109109
throw new CodePushUnknownException("Error in getting binary resources modified time", e);
110110
}
111111
}
@@ -143,44 +143,30 @@ public static String getJSBundleFile(String assetsBundleFileName) {
143143
public String getJSBundleFileInternal(String assetsBundleFileName) {
144144
this.mAssetsBundleFileName = assetsBundleFileName;
145145
String binaryJsBundleUrl = CodePushConstants.ASSETS_BUNDLE_PREFIX + assetsBundleFileName;
146-
long binaryResourcesModifiedTime = this.getBinaryResourcesModifiedTime();
147146

148-
try {
149-
String packageFilePath = mUpdateManager.getCurrentPackageBundlePath(this.mAssetsBundleFileName);
150-
if (packageFilePath == null) {
151-
// There has not been any downloaded updates.
152-
CodePushUtils.logBundleUrl(binaryJsBundleUrl);
153-
sIsRunningBinaryVersion = true;
154-
return binaryJsBundleUrl;
155-
}
147+
String packageFilePath = mUpdateManager.getCurrentPackageBundlePath(this.mAssetsBundleFileName);
148+
if (packageFilePath == null) {
149+
// There has not been any downloaded updates.
150+
CodePushUtils.logBundleUrl(binaryJsBundleUrl);
151+
sIsRunningBinaryVersion = true;
152+
return binaryJsBundleUrl;
153+
}
156154

157-
JSONObject packageMetadata = this.mUpdateManager.getCurrentPackage();
158-
Long binaryModifiedDateDuringPackageInstall = null;
159-
String binaryModifiedDateDuringPackageInstallString = packageMetadata.optString(CodePushConstants.BINARY_MODIFIED_TIME_KEY, null);
160-
if (binaryModifiedDateDuringPackageInstallString != null) {
161-
binaryModifiedDateDuringPackageInstall = Long.parseLong(binaryModifiedDateDuringPackageInstallString);
155+
JSONObject packageMetadata = this.mUpdateManager.getCurrentPackage();
156+
if (isPackageBundleLatest(packageMetadata)) {
157+
CodePushUtils.logBundleUrl(packageFilePath);
158+
sIsRunningBinaryVersion = false;
159+
return packageFilePath;
160+
} else {
161+
// The binary version is newer.
162+
this.mDidUpdate = false;
163+
if (!this.mIsDebugMode || hasBinaryVersionChanged(packageMetadata)) {
164+
this.clearUpdates();
162165
}
163166

164-
String packageAppVersion = packageMetadata.optString("appVersion", null);
165-
if (binaryModifiedDateDuringPackageInstall != null &&
166-
binaryModifiedDateDuringPackageInstall == binaryResourcesModifiedTime &&
167-
(isUsingTestConfiguration() || sAppVersion.equals(packageAppVersion))) {
168-
CodePushUtils.logBundleUrl(packageFilePath);
169-
sIsRunningBinaryVersion = false;
170-
return packageFilePath;
171-
} else {
172-
// The binary version is newer.
173-
this.mDidUpdate = false;
174-
if (!this.mIsDebugMode || !sAppVersion.equals(packageAppVersion)) {
175-
this.clearUpdates();
176-
}
177-
178-
CodePushUtils.logBundleUrl(binaryJsBundleUrl);
179-
sIsRunningBinaryVersion = true;
180-
return binaryJsBundleUrl;
181-
}
182-
} catch (NumberFormatException e) {
183-
throw new CodePushUnknownException("Error in reading binary modified date from package metadata", e);
167+
CodePushUtils.logBundleUrl(binaryJsBundleUrl);
168+
sIsRunningBinaryVersion = true;
169+
return binaryJsBundleUrl;
184170
}
185171
}
186172

@@ -195,6 +181,12 @@ void initializeUpdateAfterRestart() {
195181

196182
JSONObject pendingUpdate = mSettingsManager.getPendingUpdate();
197183
if (pendingUpdate != null) {
184+
JSONObject packageMetadata = this.mUpdateManager.getCurrentPackage();
185+
if (!isPackageBundleLatest(packageMetadata) && hasBinaryVersionChanged(packageMetadata)) {
186+
CodePushUtils.log("Skipping initializeUpdateAfterRestart(), binary version is newer");
187+
return;
188+
}
189+
198190
try {
199191
boolean updateIsLoading = pendingUpdate.getBoolean(CodePushConstants.PENDING_UPDATE_IS_LOADING_KEY);
200192
if (updateIsLoading) {
@@ -232,6 +224,28 @@ boolean isRunningBinaryVersion() {
232224
return sIsRunningBinaryVersion;
233225
}
234226

227+
private boolean isPackageBundleLatest(JSONObject packageMetadata) {
228+
try {
229+
Long binaryModifiedDateDuringPackageInstall = null;
230+
String binaryModifiedDateDuringPackageInstallString = packageMetadata.optString(CodePushConstants.BINARY_MODIFIED_TIME_KEY, null);
231+
if (binaryModifiedDateDuringPackageInstallString != null) {
232+
binaryModifiedDateDuringPackageInstall = Long.parseLong(binaryModifiedDateDuringPackageInstallString);
233+
}
234+
String packageAppVersion = packageMetadata.optString("appVersion", null);
235+
long binaryResourcesModifiedTime = this.getBinaryResourcesModifiedTime();
236+
return binaryModifiedDateDuringPackageInstall != null &&
237+
binaryModifiedDateDuringPackageInstall == binaryResourcesModifiedTime &&
238+
(isUsingTestConfiguration() || sAppVersion.equals(packageAppVersion));
239+
} catch (NumberFormatException e) {
240+
throw new CodePushUnknownException("Error in reading binary modified date from package metadata", e);
241+
}
242+
}
243+
244+
private boolean hasBinaryVersionChanged(JSONObject packageMetadata) {
245+
String packageAppVersion = packageMetadata.optString("appVersion", null);
246+
return !sAppVersion.equals(packageAppVersion);
247+
}
248+
235249
boolean needToReportRollback() {
236250
return sNeedToReportRollback;
237251
}
@@ -276,7 +290,7 @@ static ReactInstanceManager getReactInstanceManager() {
276290
}
277291
return mReactInstanceHolder.getReactInstanceManager();
278292
}
279-
293+
280294
@Override
281295
public List<NativeModule> createNativeModules(ReactApplicationContext reactApplicationContext) {
282296
CodePushNativeModule codePushModule = new CodePushNativeModule(reactApplicationContext, this, mUpdateManager, mTelemetryManager, mSettingsManager);

docs/api-js.md

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ The `codePush` decorator accepts an "options" object that allows you to customiz
118118

119119
* __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.
120120

121-
* __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`, 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, regardless how long it was in the background.
121+
* __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.
122122

123123
* __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#user-content-apple-note).
124124

@@ -179,11 +179,18 @@ See [disallowRestart](#codepushdisallowrestart) for an example of how this metho
179179
#### codePush.checkForUpdate
180180
181181
```javascript
182-
codePush.checkForUpdate(deploymentKey: String = null): Promise<RemotePackage>;
182+
codePush.checkForUpdate(deploymentKey: String = null, handleBinaryVersionMismatchCallback: (update: RemotePackage) => void): Promise<RemotePackage>;
183183
```
184184
185185
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.
186186
187+
Second optional parameter `handleBinaryVersionMismatchCallback` is an optional callback function that can be used to notify user if there are any binary update.
188+
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.
189+
190+
**NOTE:**
191+
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:
192+
> 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.
193+
187194
This method returns a `Promise` which resolves to one of two possible values:
188195
189196
1. `null` if there is no update available. This can occur in the following scenarios:
@@ -356,7 +363,7 @@ This method is for advanced scenarios, and is primarily useful when the followin
356363
#### codePush.sync
357364
358365
```javascript
359-
codePush.sync(options: Object, syncStatusChangeCallback: function(syncStatus: Number), downloadProgressCallback: function(progress: DownloadProgress)): Promise<Number>;
366+
codePush.sync(options: Object, syncStatusChangeCallback: function(syncStatus: Number), downloadProgressCallback: function(progress: DownloadProgress), handleBinaryVersionMismatchCallback: function(update: RemotePackage)): Promise<Number>;
360367
```
361368
362369
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.
@@ -429,7 +436,7 @@ codePush.sync({
429436
});
430437
```
431438
432-
In addition to the options, the `sync` method also accepts two optional function parameters which allow you to subscribe to the lifecycle of the `sync` "pipeline" in order to display additional UI as needed (e.g. a "checking for update modal or a download progress modal):
439+
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 (e.g. a "checking for update modal or a download progress modal):
433440

434441
* __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.
435442

@@ -439,6 +446,9 @@ In addition to the options, the `sync` method also accepts two optional function
439446

440447
* __receivedBytes__ *(Number)* - The number of bytes downloaded thus far, which can be used to track download progress.
441448

449+
* __handleBinaryVersionMismatchCallback__ *((update: RemotePackage) => void)* -
450+
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.
451+
442452
Example Usage:
443453

444454
```javascript
@@ -529,6 +539,8 @@ This enum specifies when you would like an installed update to actually be appli
529539
530540
* __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.
531541
542+
* __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.
543+
532544
##### CheckFrequency
533545
534546
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:

ios/CodePush/CodePush.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ @implementation CodePush {
2424
BOOL _isFirstRunAfterUpdate;
2525
int _minimumBackgroundDuration;
2626
NSDate *_lastResignedDate;
27-
CodePushInstallMode *_installMode;
27+
CodePushInstallMode _installMode;
2828
NSTimer *_appSuspendTimer;
2929

3030
// Used to coordinate the dispatching of download progress events to JS.

0 commit comments

Comments
 (0)