From eeeb4e0c33c3eb3c1d4b7dd37a64cee9447e3701 Mon Sep 17 00:00:00 2001 From: Chiraag_Arun_Kumar <39455997+chiraag918@users.noreply.github.com> Date: Mon, 28 Jul 2025 05:36:32 +0530 Subject: [PATCH 1/6] *[ft] add rollout feat --- CodePush.js | 92 ++++++++++++++++++- .../microsoft/codepush/react/CodePush.java | 3 + .../codepush/react/RolloutStorageModule.java | 39 ++++++++ .../createReleaseHistory.js | 1 + .../releaseCommand/addToReleaseHistory.js | 3 + cli/commands/releaseCommand/index.js | 3 + cli/commands/releaseCommand/release.js | 3 + cli/commands/updateHistoryCommand/index.js | 3 + .../updateReleaseHistory.js | 3 + ios/CodePush/RolloutStorage.h | 9 ++ ios/CodePush/RolloutStorage.m | 21 +++++ typings/react-native-code-push.d.ts | 9 +- 12 files changed, 185 insertions(+), 4 deletions(-) create mode 100644 android/app/src/main/java/com/microsoft/codepush/react/RolloutStorageModule.java create mode 100644 ios/CodePush/RolloutStorage.h create mode 100644 ios/CodePush/RolloutStorage.m diff --git a/CodePush.js b/CodePush.js index c55d15a22..a23afced1 100644 --- a/CodePush.js +++ b/CodePush.js @@ -6,8 +6,66 @@ import { SemverVersioning } from './versioning/SemverVersioning' let NativeCodePush = require("react-native").NativeModules.CodePush; const PackageMixins = require("./package-mixins")(NativeCodePush); +const RolloutStorage = require("react-native").NativeModules.RolloutStorage; -const DEPLOYMENT_KEY = 'deprecated_deployment_key'; +const DEPLOYMENT_KEY = 'deprecated_deployment_key', + ROLLOUT_CACHE_PREFIX = 'CodePushRolloutDecision_', + ROLLOUT_CACHE_KEY = 'CodePushRolloutKey'; + +function hashDeviceId(deviceId) { + let hash = 0; + for (let i = 0; i < deviceId.length; i++) { + hash = ((hash << 5) - hash) + deviceId.charCodeAt(i); + hash |= 0; // Convert to 32bit int + } + return Math.abs(hash); +} + +function getRolloutKey(label, rollout) { + return `${ROLLOUT_CACHE_PREFIX}${label}_rollout_${rollout ?? 'full'}`; +} + +function getBucket(clientId) { + const hash = hashDeviceId(clientId); // assume defined elsewhere + return Math.abs(hash) % 100; +} + +export async function shouldApplyCodePushUpdate(remotePackage, clientId, onRolloutSkipped) { + if (remotePackage.rollout === undefined || remotePackage.rollout >= 100) { + return true; + } + + const rolloutKey = getRolloutKey(remotePackage.label, remotePackage.rollout); + const cachedDecision = await RolloutStorage.getItem(rolloutKey); + + if (cachedDecision != null) { + const shouldApply = cachedDecision === 'true'; + if (!shouldApply) { + console.log(`[CodePush] Skipping update — rollout cached: NOT in rollout`); + onRolloutSkipped?.(remotePackage.label); + } + return shouldApply; + } + + const bucket = getBucket(clientId); + const inRollout = bucket < remotePackage.rollout; + + console.log(`[CodePush] Bucket: ${bucket}, rollout: ${remotePackage.rollout} → ${inRollout ? 'IN' : 'OUT'}`); + const prevRolloutCacheKey = await RolloutStorage.getItem(ROLLOUT_CACHE_KEY); + + if(prevRolloutCacheKey) + await RolloutStorage.removeItem(prevRolloutCacheKey); + + await RolloutStorage.setItem(ROLLOUT_CACHE_KEY, rolloutKey); + await RolloutStorage.setItem(rolloutKey, inRollout.toString()); + + if (!inRollout) { + console.log(`[CodePush] Skipping update due to rollout. Bucket ${bucket} >= rollout ${remotePackage.rollout}`); + onRolloutSkipped?.(remotePackage.label); + } + + return inRollout; +} async function checkForUpdate(handleBinaryVersionMismatchCallback = null) { /* @@ -121,6 +179,7 @@ async function checkForUpdate(handleBinaryVersionMismatchCallback = null) { package_size: 0, // not used at runtime. should_run_binary_version: false, + rollout: latestReleaseInfo.rollout }; return mapToRemotePackageMetadata(updateInfo); @@ -163,7 +222,14 @@ async function checkForUpdate(handleBinaryVersionMismatchCallback = null) { return null; } else { - const remotePackage = { ...update, ...PackageMixins.remote() }; + const remotePackage = { ...PackageMixins.remote(), ...update }; + + // Rollout filtering + const shouldApply = await shouldApplyCodePushUpdate(remotePackage, nativeConfig.clientUniqueId, sharedCodePushOptions?.onRolloutSkipped); + + if(!shouldApply && !remotePackage.enabled) + return { skipRollout: true }; + remotePackage.failedInstall = await NativeCodePush.isFailedUpdate(remotePackage.packageHash); return remotePackage; } @@ -193,6 +259,7 @@ function mapToRemotePackageMetadata(updateInfo) { packageHash: updateInfo.package_hash ?? '', packageSize: updateInfo.package_size ?? 0, downloadUrl: updateInfo.download_url ?? '', + rollout: updateInfo.rollout ?? 100, }; } @@ -443,6 +510,9 @@ async function syncInternal(options = {}, syncStatusChangeCallback, downloadProg case CodePush.SyncStatus.INSTALLING_UPDATE: log("Installing update."); break; + case CodePush.SyncStatus.CODEPUSH_SKIPPED: + log("Codepush Skipped."); + break; case CodePush.SyncStatus.UP_TO_DATE: log("App is up to date."); break; @@ -493,6 +563,11 @@ async function syncInternal(options = {}, syncStatusChangeCallback, downloadProg return CodePush.SyncStatus.UPDATE_INSTALLED; }; + if(remotePackage?.skipRollout){ + syncStatusChangeCallback(CodePush.SyncStatus.UNKNOWN_ERROR); + return CodePush.SyncStatus.UNKNOWN_ERROR; + } + const updateShouldBeIgnored = await shouldUpdateBeIgnored(remotePackage, syncOptions); if (!remotePackage || updateShouldBeIgnored) { @@ -609,6 +684,9 @@ let CodePush; * * onSyncError: (label: string, error: Error) => void | undefined, * setOnSyncError(onSyncErrorFunction: (label: string, error: Error) => void | undefined): void, + * + * onRolloutSkipped: (label: string, error: Error) => void | undefined, + * setOnRolloutSkipped(onRolloutSkippedFunction: (label: string, error: Error) => void | undefined): void, * }} */ const sharedCodePushOptions = { @@ -653,6 +731,12 @@ const sharedCodePushOptions = { if (typeof onSyncErrorFunction !== 'function') throw new Error('Please pass a function to onSyncError'); this.onSyncError = onSyncErrorFunction; }, + onRolloutSkipped: undefined, + setOnRolloutSkipped(onRolloutSkippedFunction) { + if (!onRolloutSkippedFunction) return; + if (typeof onRolloutSkippedFunction !== 'function') throw new Error('Please pass a function to onRolloutSkipped'); + this.onRolloutSkipped = onRolloutSkippedFunction; + } } function codePushify(options = {}) { @@ -688,6 +772,7 @@ function codePushify(options = {}) { sharedCodePushOptions.setOnDownloadStart(options.onDownloadStart); sharedCodePushOptions.setOnDownloadSuccess(options.onDownloadSuccess); sharedCodePushOptions.setOnSyncError(options.onSyncError); + sharedCodePushOptions.setOnRolloutSkipped(options.onRolloutSkipped); const decorator = (RootComponent) => { class CodePushComponent extends React.Component { @@ -790,7 +875,8 @@ if (NativeCodePush) { CHECKING_FOR_UPDATE: 5, AWAITING_USER_ACTION: 6, DOWNLOADING_PACKAGE: 7, - INSTALLING_UPDATE: 8 + INSTALLING_UPDATE: 8, + CODEPUSH_SKIPPED: 9 }, CheckFrequency: { ON_APP_START: 0, diff --git a/android/app/src/main/java/com/microsoft/codepush/react/CodePush.java b/android/app/src/main/java/com/microsoft/codepush/react/CodePush.java index 5e8688f41..aee9d84b9 100644 --- a/android/app/src/main/java/com/microsoft/codepush/react/CodePush.java +++ b/android/app/src/main/java/com/microsoft/codepush/react/CodePush.java @@ -10,6 +10,7 @@ import com.facebook.react.bridge.JavaScriptModule; import com.facebook.react.bridge.NativeModule; import com.facebook.react.bridge.ReactApplicationContext; +import com.microsoft.codepush.react.RolloutStorageModule; import com.facebook.react.devsupport.interfaces.DevSupportManager; import com.facebook.react.modules.debug.interfaces.DeveloperSettings; import com.facebook.react.uimanager.ViewManager; @@ -404,10 +405,12 @@ static ReactInstanceManager getReactInstanceManager() { public List createNativeModules(ReactApplicationContext reactApplicationContext) { CodePushNativeModule codePushModule = new CodePushNativeModule(reactApplicationContext, this, mUpdateManager, mTelemetryManager, mSettingsManager); CodePushDialog dialogModule = new CodePushDialog(reactApplicationContext); + RolloutStorageModule rolloutStorageModule = new RolloutStorageModule(reactApplicationContext); List nativeModules = new ArrayList<>(); nativeModules.add(codePushModule); nativeModules.add(dialogModule); + nativeModules.add(rolloutStorageModule); return nativeModules; } diff --git a/android/app/src/main/java/com/microsoft/codepush/react/RolloutStorageModule.java b/android/app/src/main/java/com/microsoft/codepush/react/RolloutStorageModule.java new file mode 100644 index 000000000..4b2e1ea38 --- /dev/null +++ b/android/app/src/main/java/com/microsoft/codepush/react/RolloutStorageModule.java @@ -0,0 +1,39 @@ +// RolloutStorageModule.java +package com.microsoft.codepush.react; + +import android.content.Context; +import android.content.SharedPreferences; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReactContextBaseJavaModule; +import com.facebook.react.bridge.ReactMethod; +import com.facebook.react.bridge.Promise; + +public class RolloutStorageModule extends ReactContextBaseJavaModule { + + private final SharedPreferences prefs; + + public RolloutStorageModule(ReactApplicationContext reactContext) { + super(reactContext); + prefs = reactContext.getSharedPreferences("CodePushPrefs", Context.MODE_PRIVATE); + } + + @Override + public String getName() { + return "RolloutStorage"; + } + + @ReactMethod + public void getItem(String key, Promise promise) { + promise.resolve(prefs.getString(key, null)); + } + + @ReactMethod + public void setItem(String key, String value) { + prefs.edit().putString(key, value).apply(); + } + + @ReactMethod + public void removeItem(String key) { + prefs.edit().remove(key).apply(); + } +} diff --git a/cli/commands/createHistoryCommand/createReleaseHistory.js b/cli/commands/createHistoryCommand/createReleaseHistory.js index ab435e794..c8a97f652 100644 --- a/cli/commands/createHistoryCommand/createReleaseHistory.js +++ b/cli/commands/createHistoryCommand/createReleaseHistory.js @@ -27,6 +27,7 @@ async function createReleaseHistory( mandatory: false, downloadUrl: "", packageHash: "", + rollout: 100 }; /** @type {ReleaseHistoryInterface} */ diff --git a/cli/commands/releaseCommand/addToReleaseHistory.js b/cli/commands/releaseCommand/addToReleaseHistory.js index 4d8ed85a0..4af97b8fc 100644 --- a/cli/commands/releaseCommand/addToReleaseHistory.js +++ b/cli/commands/releaseCommand/addToReleaseHistory.js @@ -25,6 +25,7 @@ const fs = require("fs"); * @param identifier {string?} * @param mandatory {boolean?} * @param enable {boolean?} + * @param rollout {number?} * @returns {Promise} */ async function addToReleaseHistory( @@ -38,6 +39,7 @@ async function addToReleaseHistory( identifier, mandatory, enable, + rollout ) { const releaseHistory = await getReleaseHistory(binaryVersion, platform, identifier); @@ -54,6 +56,7 @@ async function addToReleaseHistory( mandatory: mandatory, downloadUrl: bundleDownloadUrl, packageHash: packageHash, + rollout: rollout } try { diff --git a/cli/commands/releaseCommand/index.js b/cli/commands/releaseCommand/index.js index 6863a885f..f2651d662 100644 --- a/cli/commands/releaseCommand/index.js +++ b/cli/commands/releaseCommand/index.js @@ -16,6 +16,7 @@ program.command('release') .option('-j, --js-bundle-name ', 'JS bundle file name (default-ios: "main.jsbundle" / default-android: "index.android.bundle")') .option('-m, --mandatory ', 'make the release to be mandatory', parseBoolean, false) .option('--enable ', 'make the release to be enabled', parseBoolean, true) + .option('--rollout ', 'rollout percentage (0-100)', parseFloat, 100) .option('--skip-bundle ', 'skip bundle process', parseBoolean, false) .option('--skip-cleanup ', 'skip cleanup process', parseBoolean, false) .option('--output-bundle-dir ', 'name of directory containing the bundle file created by the "bundle" command', OUTPUT_BUNDLE_DIR) @@ -32,6 +33,7 @@ program.command('release') * @param {string} options.bundleName * @param {string} options.mandatory * @param {string} options.enable + * @param {number} options.rollout * @param {string} options.skipBundle * @param {string} options.skipCleanup * @param {string} options.outputBundleDir @@ -54,6 +56,7 @@ program.command('release') options.bundleName, options.mandatory, options.enable, + options.rollout, options.skipBundle, options.skipCleanup, `${options.outputPath}/${options.outputBundleDir}`, diff --git a/cli/commands/releaseCommand/release.js b/cli/commands/releaseCommand/release.js index 2aa1745fb..9f4be2230 100644 --- a/cli/commands/releaseCommand/release.js +++ b/cli/commands/releaseCommand/release.js @@ -33,6 +33,7 @@ const { addToReleaseHistory } = require("./addToReleaseHistory"); * @param jsBundleName {string} * @param mandatory {boolean} * @param enable {boolean} + * @param rollout {number} * @param skipBundle {boolean} * @param skipCleanup {boolean} * @param bundleDirectory {string} @@ -52,6 +53,7 @@ async function release( jsBundleName, mandatory, enable, + rollout, skipBundle, skipCleanup, bundleDirectory, @@ -82,6 +84,7 @@ async function release( identifier, mandatory, enable, + rollout, ) if (!skipCleanup) { diff --git a/cli/commands/updateHistoryCommand/index.js b/cli/commands/updateHistoryCommand/index.js index 7f76781bb..f2b5a14c7 100644 --- a/cli/commands/updateHistoryCommand/index.js +++ b/cli/commands/updateHistoryCommand/index.js @@ -12,6 +12,7 @@ program.command('update-history') .option('-c, --config ', 'set config file name (JS/TS)', CONFIG_FILE_NAME) .option('-m, --mandatory ', 'make the release to be mandatory', parseBoolean, undefined) .option('-e, --enable ', 'make the release to be enabled', parseBoolean, undefined) + .option('--rollout ', 'rollout percentage (0-100)', parseFloat, undefined) /** * @param {Object} options * @param {string} options.appVersion @@ -21,6 +22,7 @@ program.command('update-history') * @param {string} options.config * @param {string} options.mandatory * @param {string} options.enable + * @param {number} options.rollout * @return {void} */ .action(async (options) => { @@ -40,6 +42,7 @@ program.command('update-history') options.identifier, options.mandatory, options.enable, + options.rollout ) }); diff --git a/cli/commands/updateHistoryCommand/updateReleaseHistory.js b/cli/commands/updateHistoryCommand/updateReleaseHistory.js index d6a7e31eb..0e70db26f 100644 --- a/cli/commands/updateHistoryCommand/updateReleaseHistory.js +++ b/cli/commands/updateHistoryCommand/updateReleaseHistory.js @@ -23,6 +23,7 @@ const path = require('path'); * @param identifier {string?} * @param mandatory {boolean?} * @param enable {boolean?} + * @param rollout {number?} * @returns {Promise} */ async function updateReleaseHistory( @@ -34,6 +35,7 @@ async function updateReleaseHistory( identifier, mandatory, enable, + rollout ) { const releaseHistory = await getReleaseHistory(binaryVersion, platform, identifier); @@ -42,6 +44,7 @@ async function updateReleaseHistory( if (typeof mandatory === "boolean") updateInfo.mandatory = mandatory; if (typeof enable === "boolean") updateInfo.enabled = enable; + if (typeof rollout === "number") updateInfo.rollout = rollout; try { const JSON_FILE_NAME = `${binaryVersion}.json`; diff --git a/ios/CodePush/RolloutStorage.h b/ios/CodePush/RolloutStorage.h new file mode 100644 index 000000000..7b3de13c4 --- /dev/null +++ b/ios/CodePush/RolloutStorage.h @@ -0,0 +1,9 @@ +#import + +@interface RCT_EXTERN_MODULE(RolloutStorage, NSObject) + +RCT_EXTERN_METHOD(setItem:(NSString *)key value:(NSString *)value) +RCT_EXTERN_METHOD(getItem:(NSString *)key resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(removeItem:(NSString *)key) + +@end \ No newline at end of file diff --git a/ios/CodePush/RolloutStorage.m b/ios/CodePush/RolloutStorage.m new file mode 100644 index 000000000..9ef5a5707 --- /dev/null +++ b/ios/CodePush/RolloutStorage.m @@ -0,0 +1,21 @@ +#import "RolloutStorage.h" +#import + +@implementation RolloutStorage + +RCT_EXPORT_MODULE(); + +RCT_EXPORT_METHOD(setItem:(NSString *)key value:(NSString *)value) { + [[NSUserDefaults standardUserDefaults] setObject:value forKey:key]; +} + +RCT_EXPORT_METHOD(getItem:(NSString *)key resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { + NSString *value = [[NSUserDefaults standardUserDefaults] stringForKey:key]; + resolve(value); +} + +RCT_EXPORT_METHOD(removeItem:(NSString *)key) { + [[NSUserDefaults standardUserDefaults] removeObjectForKey:key]; +} + +@end diff --git a/typings/react-native-code-push.d.ts b/typings/react-native-code-push.d.ts index 60cf47018..e79b1de3b 100644 --- a/typings/react-native-code-push.d.ts +++ b/typings/react-native-code-push.d.ts @@ -29,6 +29,7 @@ export interface ReleaseInfo { mandatory: boolean; downloadUrl: string; packageHash: string; + rollout: number; } // from code-push SDK @@ -93,6 +94,10 @@ export interface CodePushOptions extends SyncOptions { * Callback function that is called when sync process failed. */ onSyncError?: (label: string, error: Error) => void; + /** + * Callback function that is called when rollout is skipped. + */ + onRolloutSkipped?: (label: string, error: Error) => void; } export interface DownloadProgress { @@ -342,9 +347,11 @@ declare namespace CodePush { /** * Asks the CodePush service whether the configured app deployment has an update available. * + * @param deploymentKey The deployment key to use to query the CodePush server for an update. + * * @param handleBinaryVersionMismatchCallback An optional callback for handling target binary version mismatch */ - function checkForUpdate(handleBinaryVersionMismatchCallback?: HandleBinaryVersionMismatchCallback): Promise; + function checkForUpdate(deploymentKey?: string, handleBinaryVersionMismatchCallback?: HandleBinaryVersionMismatchCallback): Promise; /** * Retrieves the metadata for an installed update (e.g. description, mandatory). From 5d271090bc459338e829125abd4ae1713bd2ed87 Mon Sep 17 00:00:00 2001 From: Chiraag_Arun_Kumar <39455997+chiraag918@users.noreply.github.com> Date: Sun, 3 Aug 2025 03:52:09 +0530 Subject: [PATCH 2/6] *[m] add packageHash & random number to bucket prediction logic --- CodePush.js | 26 +++++++++---------- .../codepush/react/RolloutStorageModule.java | 1 - 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/CodePush.js b/CodePush.js index a23afced1..3eca2d592 100644 --- a/CodePush.js +++ b/CodePush.js @@ -25,9 +25,11 @@ function getRolloutKey(label, rollout) { return `${ROLLOUT_CACHE_PREFIX}${label}_rollout_${rollout ?? 'full'}`; } -function getBucket(clientId) { - const hash = hashDeviceId(clientId); // assume defined elsewhere - return Math.abs(hash) % 100; +function getBucket(clientId, packageHash) { + const hash = hashDeviceId(`${clientId ?? ''}_${packageHash ?? ''}`), + randomFactor = Math.floor(Math.random() * 100); // 0-99 value + + return ((Math.abs(hash) % 100) * randomFactor) % 100; } export async function shouldApplyCodePushUpdate(remotePackage, clientId, onRolloutSkipped) { @@ -35,23 +37,19 @@ export async function shouldApplyCodePushUpdate(remotePackage, clientId, onRollo return true; } - const rolloutKey = getRolloutKey(remotePackage.label, remotePackage.rollout); - const cachedDecision = await RolloutStorage.getItem(rolloutKey); + const rolloutKey = getRolloutKey(remotePackage.label, remotePackage.rollout), + cachedDecision = await RolloutStorage.getItem(rolloutKey); if (cachedDecision != null) { - const shouldApply = cachedDecision === 'true'; - if (!shouldApply) { - console.log(`[CodePush] Skipping update — rollout cached: NOT in rollout`); - onRolloutSkipped?.(remotePackage.label); - } - return shouldApply; + // should apply if cachedDecision is true + return cachedDecision === 'true'; } - const bucket = getBucket(clientId); - const inRollout = bucket < remotePackage.rollout; + const bucket = getBucket(clientId, remotePackage.packageHash), + inRollout = bucket < remotePackage.rollout, + prevRolloutCacheKey = await RolloutStorage.getItem(ROLLOUT_CACHE_KEY); console.log(`[CodePush] Bucket: ${bucket}, rollout: ${remotePackage.rollout} → ${inRollout ? 'IN' : 'OUT'}`); - const prevRolloutCacheKey = await RolloutStorage.getItem(ROLLOUT_CACHE_KEY); if(prevRolloutCacheKey) await RolloutStorage.removeItem(prevRolloutCacheKey); diff --git a/android/app/src/main/java/com/microsoft/codepush/react/RolloutStorageModule.java b/android/app/src/main/java/com/microsoft/codepush/react/RolloutStorageModule.java index 4b2e1ea38..2fa6d6edb 100644 --- a/android/app/src/main/java/com/microsoft/codepush/react/RolloutStorageModule.java +++ b/android/app/src/main/java/com/microsoft/codepush/react/RolloutStorageModule.java @@ -1,4 +1,3 @@ -// RolloutStorageModule.java package com.microsoft.codepush.react; import android.content.Context; From 1bd6756f202c80220ce56988511b0bc83c455417 Mon Sep 17 00:00:00 2001 From: Chiraag_Arun_Kumar <39455997+chiraag918@users.noreply.github.com> Date: Sun, 3 Aug 2025 22:19:31 +0530 Subject: [PATCH 3/6] *[m] rm random multiplier --- CodePush.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/CodePush.js b/CodePush.js index 3eca2d592..cb3992833 100644 --- a/CodePush.js +++ b/CodePush.js @@ -26,10 +26,8 @@ function getRolloutKey(label, rollout) { } function getBucket(clientId, packageHash) { - const hash = hashDeviceId(`${clientId ?? ''}_${packageHash ?? ''}`), - randomFactor = Math.floor(Math.random() * 100); // 0-99 value - - return ((Math.abs(hash) % 100) * randomFactor) % 100; + const hash = hashDeviceId(`${clientId ?? ''}_${packageHash ?? ''}`); + return (Math.abs(hash) % 100); } export async function shouldApplyCodePushUpdate(remotePackage, clientId, onRolloutSkipped) { From 1ba36c37136b2601eabbac058e81e2889f8eadc7 Mon Sep 17 00:00:00 2001 From: Chiraag_Arun_Kumar <39455997+chiraag918@users.noreply.github.com> Date: Tue, 12 Aug 2025 02:52:41 +0530 Subject: [PATCH 4/6] *[m] address PR comments --- CodePush.js | 10 +++++----- cli/commands/releaseCommand/index.js | 1 - typings/react-native-code-push.d.ts | 4 +--- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/CodePush.js b/CodePush.js index cb3992833..d175584c9 100644 --- a/CodePush.js +++ b/CodePush.js @@ -22,7 +22,7 @@ function hashDeviceId(deviceId) { } function getRolloutKey(label, rollout) { - return `${ROLLOUT_CACHE_PREFIX}${label}_rollout_${rollout ?? 'full'}`; + return `${ROLLOUT_CACHE_PREFIX}${label}_rollout_${rollout ?? 100}`; } function getBucket(clientId, packageHash) { @@ -218,12 +218,12 @@ async function checkForUpdate(handleBinaryVersionMismatchCallback = null) { return null; } else { - const remotePackage = { ...PackageMixins.remote(), ...update }; + const remotePackage = { ...update, ...PackageMixins.remote() }; // Rollout filtering const shouldApply = await shouldApplyCodePushUpdate(remotePackage, nativeConfig.clientUniqueId, sharedCodePushOptions?.onRolloutSkipped); - if(!shouldApply && !remotePackage.enabled) + if(!shouldApply && !update) return { skipRollout: true }; remotePackage.failedInstall = await NativeCodePush.isFailedUpdate(remotePackage.packageHash); @@ -560,8 +560,8 @@ async function syncInternal(options = {}, syncStatusChangeCallback, downloadProg }; if(remotePackage?.skipRollout){ - syncStatusChangeCallback(CodePush.SyncStatus.UNKNOWN_ERROR); - return CodePush.SyncStatus.UNKNOWN_ERROR; + syncStatusChangeCallback(CodePush.SyncStatus.CODEPUSH_SKIPPED); + return CodePush.SyncStatus.CODEPUSH_SKIPPED; } const updateShouldBeIgnored = await shouldUpdateBeIgnored(remotePackage, syncOptions); diff --git a/cli/commands/releaseCommand/index.js b/cli/commands/releaseCommand/index.js index f2651d662..0617d99ed 100644 --- a/cli/commands/releaseCommand/index.js +++ b/cli/commands/releaseCommand/index.js @@ -16,7 +16,6 @@ program.command('release') .option('-j, --js-bundle-name ', 'JS bundle file name (default-ios: "main.jsbundle" / default-android: "index.android.bundle")') .option('-m, --mandatory ', 'make the release to be mandatory', parseBoolean, false) .option('--enable ', 'make the release to be enabled', parseBoolean, true) - .option('--rollout ', 'rollout percentage (0-100)', parseFloat, 100) .option('--skip-bundle ', 'skip bundle process', parseBoolean, false) .option('--skip-cleanup ', 'skip cleanup process', parseBoolean, false) .option('--output-bundle-dir ', 'name of directory containing the bundle file created by the "bundle" command', OUTPUT_BUNDLE_DIR) diff --git a/typings/react-native-code-push.d.ts b/typings/react-native-code-push.d.ts index e79b1de3b..cc38559ab 100644 --- a/typings/react-native-code-push.d.ts +++ b/typings/react-native-code-push.d.ts @@ -347,11 +347,9 @@ declare namespace CodePush { /** * Asks the CodePush service whether the configured app deployment has an update available. * - * @param deploymentKey The deployment key to use to query the CodePush server for an update. - * * @param handleBinaryVersionMismatchCallback An optional callback for handling target binary version mismatch */ - function checkForUpdate(deploymentKey?: string, handleBinaryVersionMismatchCallback?: HandleBinaryVersionMismatchCallback): Promise; + function checkForUpdate(handleBinaryVersionMismatchCallback?: HandleBinaryVersionMismatchCallback): Promise; /** * Retrieves the metadata for an installed update (e.g. description, mandatory). From 63e6c016626a357a25b168de0f0f0920a98548fd Mon Sep 17 00:00:00 2001 From: Chiraag_Arun_Kumar <39455997+chiraag918@users.noreply.github.com> Date: Tue, 12 Aug 2025 02:58:00 +0530 Subject: [PATCH 5/6] *[m] address PR comments --- CodePush.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CodePush.js b/CodePush.js index d175584c9..44c32f74a 100644 --- a/CodePush.js +++ b/CodePush.js @@ -223,8 +223,8 @@ async function checkForUpdate(handleBinaryVersionMismatchCallback = null) { // Rollout filtering const shouldApply = await shouldApplyCodePushUpdate(remotePackage, nativeConfig.clientUniqueId, sharedCodePushOptions?.onRolloutSkipped); - if(!shouldApply && !update) - return { skipRollout: true }; + if(!shouldApply) + return { skipRollout: true }; remotePackage.failedInstall = await NativeCodePush.isFailedUpdate(remotePackage.packageHash); return remotePackage; From 512609e850a8116476db984d5639e3bb5f8c696e Mon Sep 17 00:00:00 2001 From: Chiraag_Arun_Kumar <39455997+chiraag918@users.noreply.github.com> Date: Thu, 14 Aug 2025 03:23:54 +0530 Subject: [PATCH 6/6] *[m] address PR comments - refactor rollout.h --- CodePush.js | 10 +++------- cli/commands/releaseCommand/index.js | 1 + ios/CodePush/RolloutStorage.h | 9 ++------- ios/CodePush/RolloutStorage.m | 5 +++-- 4 files changed, 9 insertions(+), 16 deletions(-) diff --git a/CodePush.js b/CodePush.js index 44c32f74a..12cd2380f 100644 --- a/CodePush.js +++ b/CodePush.js @@ -506,9 +506,6 @@ async function syncInternal(options = {}, syncStatusChangeCallback, downloadProg case CodePush.SyncStatus.INSTALLING_UPDATE: log("Installing update."); break; - case CodePush.SyncStatus.CODEPUSH_SKIPPED: - log("Codepush Skipped."); - break; case CodePush.SyncStatus.UP_TO_DATE: log("App is up to date."); break; @@ -560,8 +557,8 @@ async function syncInternal(options = {}, syncStatusChangeCallback, downloadProg }; if(remotePackage?.skipRollout){ - syncStatusChangeCallback(CodePush.SyncStatus.CODEPUSH_SKIPPED); - return CodePush.SyncStatus.CODEPUSH_SKIPPED; + syncStatusChangeCallback(CodePush.SyncStatus.UP_TO_DATE); + return CodePush.SyncStatus.UP_TO_DATE; } const updateShouldBeIgnored = await shouldUpdateBeIgnored(remotePackage, syncOptions); @@ -871,8 +868,7 @@ if (NativeCodePush) { CHECKING_FOR_UPDATE: 5, AWAITING_USER_ACTION: 6, DOWNLOADING_PACKAGE: 7, - INSTALLING_UPDATE: 8, - CODEPUSH_SKIPPED: 9 + INSTALLING_UPDATE: 8 }, CheckFrequency: { ON_APP_START: 0, diff --git a/cli/commands/releaseCommand/index.js b/cli/commands/releaseCommand/index.js index 0617d99ed..fffeefe26 100644 --- a/cli/commands/releaseCommand/index.js +++ b/cli/commands/releaseCommand/index.js @@ -16,6 +16,7 @@ program.command('release') .option('-j, --js-bundle-name ', 'JS bundle file name (default-ios: "main.jsbundle" / default-android: "index.android.bundle")') .option('-m, --mandatory ', 'make the release to be mandatory', parseBoolean, false) .option('--enable ', 'make the release to be enabled', parseBoolean, true) + .option('--rollout ', 'rollout percentage (0-100)', parseFloat) .option('--skip-bundle ', 'skip bundle process', parseBoolean, false) .option('--skip-cleanup ', 'skip cleanup process', parseBoolean, false) .option('--output-bundle-dir ', 'name of directory containing the bundle file created by the "bundle" command', OUTPUT_BUNDLE_DIR) diff --git a/ios/CodePush/RolloutStorage.h b/ios/CodePush/RolloutStorage.h index 7b3de13c4..e98e78ef5 100644 --- a/ios/CodePush/RolloutStorage.h +++ b/ios/CodePush/RolloutStorage.h @@ -1,9 +1,4 @@ #import -@interface RCT_EXTERN_MODULE(RolloutStorage, NSObject) - -RCT_EXTERN_METHOD(setItem:(NSString *)key value:(NSString *)value) -RCT_EXTERN_METHOD(getItem:(NSString *)key resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) -RCT_EXTERN_METHOD(removeItem:(NSString *)key) - -@end \ No newline at end of file +@interface RolloutStorage : NSObject +@end diff --git a/ios/CodePush/RolloutStorage.m b/ios/CodePush/RolloutStorage.m index 9ef5a5707..76f463002 100644 --- a/ios/CodePush/RolloutStorage.m +++ b/ios/CodePush/RolloutStorage.m @@ -1,5 +1,4 @@ #import "RolloutStorage.h" -#import @implementation RolloutStorage @@ -9,7 +8,9 @@ @implementation RolloutStorage [[NSUserDefaults standardUserDefaults] setObject:value forKey:key]; } -RCT_EXPORT_METHOD(getItem:(NSString *)key resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { +RCT_EXPORT_METHOD(getItem:(NSString *)key + resolver:(RCTPromiseResolveBlock)resolve + rejecter:(RCTPromiseRejectBlock)reject) { NSString *value = [[NSUserDefaults standardUserDefaults] stringForKey:key]; resolve(value); }