@@ -6,12 +6,13 @@ import { SemverVersioning } from './versioning/SemverVersioning'
6
6
7
7
let NativeCodePush = require ( "react-native" ) . NativeModules . CodePush ;
8
8
const PackageMixins = require ( "./package-mixins" ) ( NativeCodePush ) ;
9
- const RolloutStorage = require ( "react-native" ) . NativeModules . RolloutStorage ;
10
9
11
- const DEPLOYMENT_KEY = 'deprecated_deployment_key' ,
12
- ROLLOUT_CACHE_PREFIX = 'CodePushRolloutDecision_' ,
13
- ROLLOUT_CACHE_KEY = 'CodePushRolloutKey' ;
10
+ const DEPLOYMENT_KEY = 'deprecated_deployment_key' ;
14
11
12
+ /**
13
+ * @param deviceId {string}
14
+ * @returns {number }
15
+ */
15
16
function hashDeviceId ( deviceId ) {
16
17
let hash = 0 ;
17
18
for ( let i = 0 ; i < deviceId . length ; i ++ ) {
@@ -21,43 +22,36 @@ function hashDeviceId(deviceId) {
21
22
return Math . abs ( hash ) ;
22
23
}
23
24
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
+ */
28
30
function getBucket ( clientId , packageHash ) {
29
31
const hash = hashDeviceId ( `${ clientId ?? '' } _${ packageHash ?? '' } ` ) ;
30
32
return ( Math . abs ( hash ) % 100 ) ;
31
33
}
32
34
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 ( ) ;
40
42
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 ;
44
45
}
45
46
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 ;
51
49
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' } ` ) ;
57
51
58
52
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 ) ;
61
55
}
62
56
63
57
return inRollout ;
@@ -96,6 +90,9 @@ async function checkForUpdate(handleBinaryVersionMismatchCallback = null) {
96
90
}
97
91
}
98
92
93
+ /**
94
+ * @type {RemotePackage|null|undefined }
95
+ */
99
96
const update = await ( async ( ) => {
100
97
try {
101
98
const updateRequest = {
@@ -112,8 +109,8 @@ async function checkForUpdate(handleBinaryVersionMismatchCallback = null) {
112
109
*/
113
110
const updateChecker = sharedCodePushOptions . updateChecker ;
114
111
if ( updateChecker ) {
112
+ // We do not provide rollout functionality. This could be implemented in the `updateChecker`.
115
113
const { update_info } = await updateChecker ( updateRequest ) ;
116
-
117
114
return mapToRemotePackageMetadata ( update_info ) ;
118
115
} else {
119
116
/**
@@ -131,6 +128,9 @@ async function checkForUpdate(handleBinaryVersionMismatchCallback = null) {
131
128
132
129
const versioning = new SemverVersioning ( releaseHistory ) ;
133
130
131
+ const isInRollout = await decideLatestReleaseIsInRollout ( versioning , nativeConfig . clientUniqueId , sharedCodePushOptions ?. onRolloutSkipped ) ;
132
+ versioning . setIsLatestReleaseInRollout ( isInRollout ) ;
133
+
134
134
const shouldRollbackToBinary = versioning . shouldRollbackToBinary ( runtimeVersion )
135
135
if ( shouldRollbackToBinary ) {
136
136
// Reset to latest major version and restart
@@ -175,7 +175,6 @@ async function checkForUpdate(handleBinaryVersionMismatchCallback = null) {
175
175
package_size : 0 ,
176
176
// not used at runtime.
177
177
should_run_binary_version : false ,
178
- rollout : latestReleaseInfo . rollout
179
178
} ;
180
179
181
180
return mapToRemotePackageMetadata ( updateInfo ) ;
@@ -219,13 +218,6 @@ async function checkForUpdate(handleBinaryVersionMismatchCallback = null) {
219
218
return null ;
220
219
} else {
221
220
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
-
229
221
remotePackage . failedInstall = await NativeCodePush . isFailedUpdate ( remotePackage . packageHash ) ;
230
222
return remotePackage ;
231
223
}
@@ -255,7 +247,6 @@ function mapToRemotePackageMetadata(updateInfo) {
255
247
packageHash : updateInfo . package_hash ?? '' ,
256
248
packageSize : updateInfo . package_size ?? 0 ,
257
249
downloadUrl : updateInfo . download_url ?? '' ,
258
- rollout : updateInfo . rollout ?? 100 ,
259
250
} ;
260
251
}
261
252
@@ -280,7 +271,7 @@ async function getCurrentPackage() {
280
271
async function getUpdateMetadata ( updateState ) {
281
272
let updateMetadata = await NativeCodePush . getUpdateMetadata ( updateState || CodePush . UpdateState . RUNNING ) ;
282
273
if ( updateMetadata ) {
283
- updateMetadata = { ...PackageMixins . local , ...updateMetadata } ;
274
+ updateMetadata = { ...PackageMixins . local , ...updateMetadata } ;
284
275
updateMetadata . failedInstall = await NativeCodePush . isFailedUpdate ( updateMetadata . packageHash ) ;
285
276
updateMetadata . isFirstRun = await NativeCodePush . isFirstRun ( updateMetadata . packageHash ) ;
286
277
}
@@ -487,47 +478,47 @@ async function syncInternal(options = {}, syncStatusChangeCallback, downloadProg
487
478
mandatoryInstallMode : CodePush . InstallMode . IMMEDIATE ,
488
479
minimumBackgroundDuration : 0 ,
489
480
updateDialog : null ,
490
- ...options
481
+ ...options ,
491
482
} ;
492
483
493
484
syncStatusChangeCallback = typeof syncStatusChangeCallback === "function"
494
485
? syncStatusChangeCallback
495
486
: ( 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." ) ;
524
514
}
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
+ } ;
531
522
532
523
let remotePackageLabel ;
533
524
try {
@@ -556,16 +547,11 @@ async function syncInternal(options = {}, syncStatusChangeCallback, downloadProg
556
547
return CodePush . SyncStatus . UPDATE_INSTALLED ;
557
548
} ;
558
549
559
- if ( remotePackage ?. skipRollout ) {
560
- syncStatusChangeCallback ( CodePush . SyncStatus . UP_TO_DATE ) ;
561
- return CodePush . SyncStatus . UP_TO_DATE ;
562
- }
563
-
564
550
const updateShouldBeIgnored = await shouldUpdateBeIgnored ( remotePackage , syncOptions ) ;
565
551
566
552
if ( ! remotePackage || updateShouldBeIgnored ) {
567
553
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." ) ;
569
555
}
570
556
571
557
const currentPackage = await CodePush . getCurrentPackage ( ) ;
@@ -604,18 +590,18 @@ async function syncInternal(options = {}, syncStatusChangeCallback, downloadProg
604
590
onPress : ( ) => {
605
591
syncStatusChangeCallback ( CodePush . SyncStatus . UPDATE_IGNORED ) ;
606
592
resolve ( CodePush . SyncStatus . UPDATE_IGNORED ) ;
607
- }
593
+ } ,
608
594
} ) ;
609
595
}
610
596
611
597
// Since the install button should be placed to the
612
598
// right of any other button, add it last
613
599
dialogButtons . push ( {
614
600
text : installButtonText ,
615
- onPress :( ) => {
601
+ onPress : ( ) => {
616
602
doDownloadAndInstall ( )
617
603
. then ( resolve , reject ) ;
618
- }
604
+ } ,
619
605
} )
620
606
621
607
// If the update has a description, and the developer
@@ -677,7 +663,7 @@ let CodePush;
677
663
*
678
664
* onSyncError: (label: string, error: Error) => void | undefined,
679
665
* setOnSyncError(onSyncErrorFunction: (label: string, error: Error) => void | undefined): void,
680
- *
666
+ *
681
667
* onRolloutSkipped: (label: string, error: Error) => void | undefined,
682
668
* setOnRolloutSkipped(onRolloutSkippedFunction: (label: string, error: Error) => void | undefined): void,
683
669
* }}
@@ -729,7 +715,7 @@ const sharedCodePushOptions = {
729
715
if ( ! onRolloutSkippedFunction ) return ;
730
716
if ( typeof onRolloutSkippedFunction !== 'function' ) throw new Error ( 'Please pass a function to onRolloutSkipped' ) ;
731
717
this . onRolloutSkipped = onRolloutSkippedFunction ;
732
- }
718
+ } ,
733
719
}
734
720
735
721
function codePushify ( options = { } ) {
@@ -748,7 +734,7 @@ function codePushify(options = {}) {
748
734
throw new Error (
749
735
`Unable to find the "Component" class, please either:
750
736
1. 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` ,
752
738
) ;
753
739
}
754
740
@@ -808,7 +794,7 @@ function codePushify(options = {}) {
808
794
}
809
795
810
796
render ( ) {
811
- const props = { ...this . props } ;
797
+ const props = { ...this . props } ;
812
798
813
799
// We can set ref property on class components only (not stateless)
814
800
// Check it by render method
@@ -855,7 +841,7 @@ if (NativeCodePush) {
855
841
IMMEDIATE : NativeCodePush . codePushInstallModeImmediate , // Restart the app immediately
856
842
ON_NEXT_RESTART : NativeCodePush . codePushInstallModeOnNextRestart , // Don't artificially restart the app. Allow the update to be "picked up" on the next app restart
857
843
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,
859
845
// but only after it has been in the background for "minimumBackgroundDuration" seconds (0 by default),
860
846
// so that user context isn't lost unless the app suspension is long enough to not matter
861
847
} ,
@@ -868,17 +854,17 @@ if (NativeCodePush) {
868
854
CHECKING_FOR_UPDATE : 5 ,
869
855
AWAITING_USER_ACTION : 6 ,
870
856
DOWNLOADING_PACKAGE : 7 ,
871
- INSTALLING_UPDATE : 8
857
+ INSTALLING_UPDATE : 8 ,
872
858
} ,
873
859
CheckFrequency : {
874
860
ON_APP_START : 0 ,
875
861
ON_APP_RESUME : 1 ,
876
- MANUAL : 2
862
+ MANUAL : 2 ,
877
863
} ,
878
864
UpdateState : {
879
865
RUNNING : NativeCodePush . codePushUpdateStateRunning ,
880
866
PENDING : NativeCodePush . codePushUpdateStatePending ,
881
- LATEST : NativeCodePush . codePushUpdateStateLatest
867
+ LATEST : NativeCodePush . codePushUpdateStateLatest ,
882
868
} ,
883
869
DeploymentStatus : {
884
870
FAILED : "DeploymentFailed" ,
@@ -892,11 +878,11 @@ if (NativeCodePush) {
892
878
optionalIgnoreButtonLabel : "Ignore" ,
893
879
optionalInstallButtonLabel : "Install" ,
894
880
optionalUpdateMessage : "An update is available. Would you like to install it?" ,
895
- title : "Update available"
881
+ title : "Update available" ,
896
882
} ,
897
883
DEFAULT_ROLLBACK_RETRY_OPTIONS : {
898
884
delayInHours : 24 ,
899
- maxRetryAttempts : 1
885
+ maxRetryAttempts : 1 ,
900
886
} ,
901
887
} ) ;
902
888
} else {
0 commit comments