@@ -6,12 +6,13 @@ import { SemverVersioning } from './versioning/SemverVersioning'
66
77let NativeCodePush = require ( "react-native" ) . NativeModules . CodePush ;
88const PackageMixins = require ( "./package-mixins" ) ( NativeCodePush ) ;
9- const RolloutStorage = require ( "react-native" ) . NativeModules . RolloutStorage ;
109
11- const DEPLOYMENT_KEY = 'deprecated_deployment_key' ,
12- ROLLOUT_CACHE_PREFIX = 'CodePushRolloutDecision_' ,
13- ROLLOUT_CACHE_KEY = 'CodePushRolloutKey' ;
10+ const DEPLOYMENT_KEY = 'deprecated_deployment_key' ;
1411
12+ /**
13+ * @param deviceId {string}
14+ * @returns {number }
15+ */
1516function hashDeviceId ( deviceId ) {
1617 let hash = 0 ;
1718 for ( let i = 0 ; i < deviceId . length ; i ++ ) {
@@ -21,43 +22,36 @@ function hashDeviceId(deviceId) {
2122 return Math . abs ( hash ) ;
2223}
2324
24- function getRolloutKey ( label , rollout ) {
25- return `${ ROLLOUT_CACHE_PREFIX } ${ label } _rollout_${ rollout ?? 100 } ` ;
26- }
27-
25+ /**
26+ * @param clientId {string}
27+ * @param packageHash {string}
28+ * @returns {number }
29+ */
2830function getBucket ( clientId , packageHash ) {
2931 const hash = hashDeviceId ( `${ clientId ?? '' } _${ packageHash ?? '' } ` ) ;
3032 return ( Math . abs ( hash ) % 100 ) ;
3133}
3234
33- export async function shouldApplyCodePushUpdate ( remotePackage , clientId , onRolloutSkipped ) {
34- if ( remotePackage . rollout === undefined || remotePackage . rollout >= 100 ) {
35- return true ;
36- }
37-
38- const rolloutKey = getRolloutKey ( remotePackage . label , remotePackage . rollout ) ,
39- cachedDecision = await RolloutStorage . getItem ( rolloutKey ) ;
35+ /**
36+ * Note that the `clientUniqueId` value may not guarantee the same value if the app is deleted and re-installed.
37+ * In other words, if a user re-installs the app, the result of this function may change.
38+ * @returns { Promise<boolean> }
39+ */
40+ async function decideLatestReleaseIsInRollout ( versioning , clientId , onRolloutSkipped ) {
41+ const [ latestVersion , latestReleaseInfo ] = versioning . findLatestRelease ( ) ;
4042
41- if ( cachedDecision != null ) {
42- // should apply if cachedDecision is true
43- return cachedDecision === 'true' ;
43+ if ( latestReleaseInfo . rollout === undefined || latestReleaseInfo . rollout >= 100 ) {
44+ return true ;
4445 }
4546
46- const bucket = getBucket ( clientId , remotePackage . packageHash ) ,
47- inRollout = bucket < remotePackage . rollout ,
48- prevRolloutCacheKey = await RolloutStorage . getItem ( ROLLOUT_CACHE_KEY ) ;
49-
50- console . log ( `[CodePush] Bucket: ${ bucket } , rollout: ${ remotePackage . rollout } → ${ inRollout ? 'IN' : 'OUT' } ` ) ;
47+ const bucket = getBucket ( clientId , latestReleaseInfo . packageHash ) ;
48+ const inRollout = bucket < latestReleaseInfo . rollout ;
5149
52- if ( prevRolloutCacheKey )
53- await RolloutStorage . removeItem ( prevRolloutCacheKey ) ;
54-
55- await RolloutStorage . setItem ( ROLLOUT_CACHE_KEY , rolloutKey ) ;
56- await RolloutStorage . setItem ( rolloutKey , inRollout . toString ( ) ) ;
50+ log ( `Bucket: ${ bucket } , rollout: ${ latestReleaseInfo . rollout } → ${ inRollout ? 'IN' : 'OUT' } ` ) ;
5751
5852 if ( ! inRollout ) {
59- console . log ( `[CodePush] Skipping update due to rollout. Bucket ${ bucket } >= rollout ${ remotePackage . rollout } ` ) ;
60- onRolloutSkipped ?. ( remotePackage . label ) ;
53+ log ( `Skipping update due to rollout. Bucket ${ bucket } is not smaller than rollout range ${ latestReleaseInfo . rollout } . ` ) ;
54+ onRolloutSkipped ?. ( latestVersion ) ;
6155 }
6256
6357 return inRollout ;
@@ -96,6 +90,9 @@ async function checkForUpdate(handleBinaryVersionMismatchCallback = null) {
9690 }
9791 }
9892
93+ /**
94+ * @type {RemotePackage|null|undefined }
95+ */
9996 const update = await ( async ( ) => {
10097 try {
10198 const updateRequest = {
@@ -112,8 +109,8 @@ async function checkForUpdate(handleBinaryVersionMismatchCallback = null) {
112109 */
113110 const updateChecker = sharedCodePushOptions . updateChecker ;
114111 if ( updateChecker ) {
112+ // We do not provide rollout functionality. This could be implemented in the `updateChecker`.
115113 const { update_info } = await updateChecker ( updateRequest ) ;
116-
117114 return mapToRemotePackageMetadata ( update_info ) ;
118115 } else {
119116 /**
@@ -131,6 +128,9 @@ async function checkForUpdate(handleBinaryVersionMismatchCallback = null) {
131128
132129 const versioning = new SemverVersioning ( releaseHistory ) ;
133130
131+ const isInRollout = await decideLatestReleaseIsInRollout ( versioning , nativeConfig . clientUniqueId , sharedCodePushOptions ?. onRolloutSkipped ) ;
132+ versioning . setIsLatestReleaseInRollout ( isInRollout ) ;
133+
134134 const shouldRollbackToBinary = versioning . shouldRollbackToBinary ( runtimeVersion )
135135 if ( shouldRollbackToBinary ) {
136136 // Reset to latest major version and restart
@@ -175,7 +175,6 @@ async function checkForUpdate(handleBinaryVersionMismatchCallback = null) {
175175 package_size : 0 ,
176176 // not used at runtime.
177177 should_run_binary_version : false ,
178- rollout : latestReleaseInfo . rollout
179178 } ;
180179
181180 return mapToRemotePackageMetadata ( updateInfo ) ;
@@ -219,13 +218,6 @@ async function checkForUpdate(handleBinaryVersionMismatchCallback = null) {
219218 return null ;
220219 } else {
221220 const remotePackage = { ...update , ...PackageMixins . remote ( ) } ;
222-
223- // Rollout filtering
224- const shouldApply = await shouldApplyCodePushUpdate ( remotePackage , nativeConfig . clientUniqueId , sharedCodePushOptions ?. onRolloutSkipped ) ;
225-
226- if ( ! shouldApply )
227- return { skipRollout : true } ;
228-
229221 remotePackage . failedInstall = await NativeCodePush . isFailedUpdate ( remotePackage . packageHash ) ;
230222 return remotePackage ;
231223 }
@@ -255,7 +247,6 @@ function mapToRemotePackageMetadata(updateInfo) {
255247 packageHash : updateInfo . package_hash ?? '' ,
256248 packageSize : updateInfo . package_size ?? 0 ,
257249 downloadUrl : updateInfo . download_url ?? '' ,
258- rollout : updateInfo . rollout ?? 100 ,
259250 } ;
260251}
261252
@@ -280,7 +271,7 @@ async function getCurrentPackage() {
280271async function getUpdateMetadata ( updateState ) {
281272 let updateMetadata = await NativeCodePush . getUpdateMetadata ( updateState || CodePush . UpdateState . RUNNING ) ;
282273 if ( updateMetadata ) {
283- updateMetadata = { ...PackageMixins . local , ...updateMetadata } ;
274+ updateMetadata = { ...PackageMixins . local , ...updateMetadata } ;
284275 updateMetadata . failedInstall = await NativeCodePush . isFailedUpdate ( updateMetadata . packageHash ) ;
285276 updateMetadata . isFirstRun = await NativeCodePush . isFirstRun ( updateMetadata . packageHash ) ;
286277 }
@@ -487,47 +478,47 @@ async function syncInternal(options = {}, syncStatusChangeCallback, downloadProg
487478 mandatoryInstallMode : CodePush . InstallMode . IMMEDIATE ,
488479 minimumBackgroundDuration : 0 ,
489480 updateDialog : null ,
490- ...options
481+ ...options ,
491482 } ;
492483
493484 syncStatusChangeCallback = typeof syncStatusChangeCallback === "function"
494485 ? syncStatusChangeCallback
495486 : ( syncStatus ) => {
496- switch ( syncStatus ) {
497- case CodePush . SyncStatus . CHECKING_FOR_UPDATE :
498- log ( "Checking for update." ) ;
499- break ;
500- case CodePush . SyncStatus . AWAITING_USER_ACTION :
501- log ( "Awaiting user action." ) ;
502- break ;
503- case CodePush . SyncStatus . DOWNLOADING_PACKAGE :
504- log ( "Downloading package." ) ;
505- break ;
506- case CodePush . SyncStatus . INSTALLING_UPDATE :
507- log ( "Installing update." ) ;
508- break ;
509- case CodePush . SyncStatus . UP_TO_DATE :
510- log ( "App is up to date." ) ;
511- break ;
512- case CodePush . SyncStatus . UPDATE_IGNORED :
513- log ( "User cancelled the update." ) ;
514- break ;
515- case CodePush . SyncStatus . UPDATE_INSTALLED :
516- if ( resolvedInstallMode == CodePush . InstallMode . ON_NEXT_RESTART ) {
517- log ( "Update is installed and will be run on the next app restart." ) ;
518- } else if ( resolvedInstallMode == CodePush . InstallMode . ON_NEXT_RESUME ) {
519- if ( syncOptions . minimumBackgroundDuration > 0 ) {
520- log ( `Update is installed and will be run after the app has been in the background for at least ${ syncOptions . minimumBackgroundDuration } seconds.` ) ;
521- } else {
522- log ( "Update is installed and will be run when the app next resumes." ) ;
523- }
487+ switch ( syncStatus ) {
488+ case CodePush . SyncStatus . CHECKING_FOR_UPDATE :
489+ log ( "Checking for update." ) ;
490+ break ;
491+ case CodePush . SyncStatus . AWAITING_USER_ACTION :
492+ log ( "Awaiting user action." ) ;
493+ break ;
494+ case CodePush . SyncStatus . DOWNLOADING_PACKAGE :
495+ log ( "Downloading package." ) ;
496+ break ;
497+ case CodePush . SyncStatus . INSTALLING_UPDATE :
498+ log ( "Installing update." ) ;
499+ break ;
500+ case CodePush . SyncStatus . UP_TO_DATE :
501+ log ( "App is up to date." ) ;
502+ break ;
503+ case CodePush . SyncStatus . UPDATE_IGNORED :
504+ log ( "User cancelled the update." ) ;
505+ break ;
506+ case CodePush . SyncStatus . UPDATE_INSTALLED :
507+ if ( resolvedInstallMode == CodePush . InstallMode . ON_NEXT_RESTART ) {
508+ log ( "Update is installed and will be run on the next app restart." ) ;
509+ } else if ( resolvedInstallMode == CodePush . InstallMode . ON_NEXT_RESUME ) {
510+ if ( syncOptions . minimumBackgroundDuration > 0 ) {
511+ log ( `Update is installed and will be run after the app has been in the background for at least ${ syncOptions . minimumBackgroundDuration } seconds.` ) ;
512+ } else {
513+ log ( "Update is installed and will be run when the app next resumes." ) ;
524514 }
525- break ;
526- case CodePush . SyncStatus . UNKNOWN_ERROR :
527- log ( "An unknown error occurred." ) ;
528- break ;
529- }
530- } ;
515+ }
516+ break ;
517+ case CodePush . SyncStatus . UNKNOWN_ERROR :
518+ log ( "An unknown error occurred." ) ;
519+ break ;
520+ }
521+ } ;
531522
532523 let remotePackageLabel ;
533524 try {
@@ -556,16 +547,11 @@ async function syncInternal(options = {}, syncStatusChangeCallback, downloadProg
556547 return CodePush . SyncStatus . UPDATE_INSTALLED ;
557548 } ;
558549
559- if ( remotePackage ?. skipRollout ) {
560- syncStatusChangeCallback ( CodePush . SyncStatus . UP_TO_DATE ) ;
561- return CodePush . SyncStatus . UP_TO_DATE ;
562- }
563-
564550 const updateShouldBeIgnored = await shouldUpdateBeIgnored ( remotePackage , syncOptions ) ;
565551
566552 if ( ! remotePackage || updateShouldBeIgnored ) {
567553 if ( updateShouldBeIgnored ) {
568- log ( "An update is available, but it is being ignored due to having been previously rolled back." ) ;
554+ log ( "An update is available, but it is being ignored due to having been previously rolled back." ) ;
569555 }
570556
571557 const currentPackage = await CodePush . getCurrentPackage ( ) ;
@@ -604,18 +590,18 @@ async function syncInternal(options = {}, syncStatusChangeCallback, downloadProg
604590 onPress : ( ) => {
605591 syncStatusChangeCallback ( CodePush . SyncStatus . UPDATE_IGNORED ) ;
606592 resolve ( CodePush . SyncStatus . UPDATE_IGNORED ) ;
607- }
593+ } ,
608594 } ) ;
609595 }
610596
611597 // Since the install button should be placed to the
612598 // right of any other button, add it last
613599 dialogButtons . push ( {
614600 text : installButtonText ,
615- onPress :( ) => {
601+ onPress : ( ) => {
616602 doDownloadAndInstall ( )
617603 . then ( resolve , reject ) ;
618- }
604+ } ,
619605 } )
620606
621607 // If the update has a description, and the developer
@@ -677,7 +663,7 @@ let CodePush;
677663 *
678664 * onSyncError: (label: string, error: Error) => void | undefined,
679665 * setOnSyncError(onSyncErrorFunction: (label: string, error: Error) => void | undefined): void,
680- *
666+ *
681667 * onRolloutSkipped: (label: string, error: Error) => void | undefined,
682668 * setOnRolloutSkipped(onRolloutSkippedFunction: (label: string, error: Error) => void | undefined): void,
683669 * }}
@@ -729,7 +715,7 @@ const sharedCodePushOptions = {
729715 if ( ! onRolloutSkippedFunction ) return ;
730716 if ( typeof onRolloutSkippedFunction !== 'function' ) throw new Error ( 'Please pass a function to onRolloutSkipped' ) ;
731717 this . onRolloutSkipped = onRolloutSkippedFunction ;
732- }
718+ } ,
733719}
734720
735721function codePushify ( options = { } ) {
@@ -748,7 +734,7 @@ function codePushify(options = {}) {
748734 throw new Error (
749735`Unable to find the "Component" class, please either:
7507361. Upgrade to a newer version of React Native that supports it, or
751- 2. Call the codePush.sync API in your component instead of using the @codePush decorator`
737+ 2. Call the codePush.sync API in your component instead of using the @codePush decorator` ,
752738 ) ;
753739 }
754740
@@ -808,7 +794,7 @@ function codePushify(options = {}) {
808794 }
809795
810796 render ( ) {
811- const props = { ...this . props } ;
797+ const props = { ...this . props } ;
812798
813799 // We can set ref property on class components only (not stateless)
814800 // Check it by render method
@@ -855,7 +841,7 @@ if (NativeCodePush) {
855841 IMMEDIATE : NativeCodePush . codePushInstallModeImmediate , // Restart the app immediately
856842 ON_NEXT_RESTART : NativeCodePush . codePushInstallModeOnNextRestart , // Don't artificially restart the app. Allow the update to be "picked up" on the next app restart
857843 ON_NEXT_RESUME : NativeCodePush . codePushInstallModeOnNextResume , // Restart the app the next time it is resumed from the background
858- ON_NEXT_SUSPEND : NativeCodePush . codePushInstallModeOnNextSuspend // Restart the app _while_ it is in the background,
844+ ON_NEXT_SUSPEND : NativeCodePush . codePushInstallModeOnNextSuspend , // Restart the app _while_ it is in the background,
859845 // but only after it has been in the background for "minimumBackgroundDuration" seconds (0 by default),
860846 // so that user context isn't lost unless the app suspension is long enough to not matter
861847 } ,
@@ -868,17 +854,17 @@ if (NativeCodePush) {
868854 CHECKING_FOR_UPDATE : 5 ,
869855 AWAITING_USER_ACTION : 6 ,
870856 DOWNLOADING_PACKAGE : 7 ,
871- INSTALLING_UPDATE : 8
857+ INSTALLING_UPDATE : 8 ,
872858 } ,
873859 CheckFrequency : {
874860 ON_APP_START : 0 ,
875861 ON_APP_RESUME : 1 ,
876- MANUAL : 2
862+ MANUAL : 2 ,
877863 } ,
878864 UpdateState : {
879865 RUNNING : NativeCodePush . codePushUpdateStateRunning ,
880866 PENDING : NativeCodePush . codePushUpdateStatePending ,
881- LATEST : NativeCodePush . codePushUpdateStateLatest
867+ LATEST : NativeCodePush . codePushUpdateStateLatest ,
882868 } ,
883869 DeploymentStatus : {
884870 FAILED : "DeploymentFailed" ,
@@ -892,11 +878,11 @@ if (NativeCodePush) {
892878 optionalIgnoreButtonLabel : "Ignore" ,
893879 optionalInstallButtonLabel : "Install" ,
894880 optionalUpdateMessage : "An update is available. Would you like to install it?" ,
895- title : "Update available"
881+ title : "Update available" ,
896882 } ,
897883 DEFAULT_ROLLBACK_RETRY_OPTIONS : {
898884 delayInHours : 24 ,
899- maxRetryAttempts : 1
885+ maxRetryAttempts : 1 ,
900886 } ,
901887 } ) ;
902888} else {
0 commit comments